diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index aa5b36a..d0bf2cb 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -19,14 +19,6 @@ _mediator = mediator; } - [HttpGet] - public async Task MyOrders() - { - var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); - - return View(viewModel); - } - [HttpGet("{orderId}")] public async Task Detail(int orderId) { diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index aa5b36a..d0bf2cb 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -19,14 +19,6 @@ _mediator = mediator; } - [HttpGet] - public async Task MyOrders() - { - var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); - - return View(viewModel); - } - [HttpGet("{orderId}")] public async Task Detail(int orderId) { diff --git a/src/Web/Features/MyOrders/GetMyOrders.cs b/src/Web/Features/MyOrders/GetMyOrders.cs deleted file mode 100644 index b99a9da..0000000 --- a/src/Web/Features/MyOrders/GetMyOrders.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; -using Microsoft.eShopWeb.Web.ViewModels; -using System.Collections.Generic; - -namespace Microsoft.eShopWeb.Web.Features.MyOrders -{ - public class GetMyOrders : IRequest> - { - public string UserName { get; set; } - - public GetMyOrders(string userName) - { - UserName = userName; - } - } -} diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index aa5b36a..d0bf2cb 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -19,14 +19,6 @@ _mediator = mediator; } - [HttpGet] - public async Task MyOrders() - { - var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); - - return View(viewModel); - } - [HttpGet("{orderId}")] public async Task Detail(int orderId) { diff --git a/src/Web/Features/MyOrders/GetMyOrders.cs b/src/Web/Features/MyOrders/GetMyOrders.cs deleted file mode 100644 index b99a9da..0000000 --- a/src/Web/Features/MyOrders/GetMyOrders.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; -using Microsoft.eShopWeb.Web.ViewModels; -using System.Collections.Generic; - -namespace Microsoft.eShopWeb.Web.Features.MyOrders -{ - public class GetMyOrders : IRequest> - { - public string UserName { get; set; } - - public GetMyOrders(string userName) - { - UserName = userName; - } - } -} diff --git a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs index e66c38a..3d6c5e4 100644 --- a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs +++ b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs @@ -9,35 +9,5 @@ namespace Microsoft.eShopWeb.Web.Features.MyOrders { - public class GetMyOrdersHandler : IRequestHandler> - { - private readonly IOrderRepository _orderRepository; - public GetMyOrdersHandler(IOrderRepository orderRepository) - { - _orderRepository = orderRepository; - } - - public async Task> Handle(GetMyOrders request, CancellationToken cancellationToken) - { - var specification = new CustomerOrdersWithItemsSpecification(request.UserName); - var orders = await _orderRepository.ListAsync(specification, cancellationToken); - - return orders.Select(o => new OrderViewModel - { - OrderDate = o.OrderDate, - OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() - { - PictureUrl = oi.ItemOrdered.PictureUri, - ProductId = oi.ItemOrdered.CatalogItemId, - ProductName = oi.ItemOrdered.ProductName, - UnitPrice = oi.UnitPrice, - Units = oi.Units - }).ToList(), - OrderNumber = o.Id, - ShippingAddress = o.ShipToAddress, - Total = o.Total() - }); - } - } } diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index aa5b36a..d0bf2cb 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -19,14 +19,6 @@ _mediator = mediator; } - [HttpGet] - public async Task MyOrders() - { - var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); - - return View(viewModel); - } - [HttpGet("{orderId}")] public async Task Detail(int orderId) { diff --git a/src/Web/Features/MyOrders/GetMyOrders.cs b/src/Web/Features/MyOrders/GetMyOrders.cs deleted file mode 100644 index b99a9da..0000000 --- a/src/Web/Features/MyOrders/GetMyOrders.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; -using Microsoft.eShopWeb.Web.ViewModels; -using System.Collections.Generic; - -namespace Microsoft.eShopWeb.Web.Features.MyOrders -{ - public class GetMyOrders : IRequest> - { - public string UserName { get; set; } - - public GetMyOrders(string userName) - { - UserName = userName; - } - } -} diff --git a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs index e66c38a..3d6c5e4 100644 --- a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs +++ b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs @@ -9,35 +9,5 @@ namespace Microsoft.eShopWeb.Web.Features.MyOrders { - public class GetMyOrdersHandler : IRequestHandler> - { - private readonly IOrderRepository _orderRepository; - public GetMyOrdersHandler(IOrderRepository orderRepository) - { - _orderRepository = orderRepository; - } - - public async Task> Handle(GetMyOrders request, CancellationToken cancellationToken) - { - var specification = new CustomerOrdersWithItemsSpecification(request.UserName); - var orders = await _orderRepository.ListAsync(specification, cancellationToken); - - return orders.Select(o => new OrderViewModel - { - OrderDate = o.OrderDate, - OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() - { - PictureUrl = oi.ItemOrdered.PictureUri, - ProductId = oi.ItemOrdered.CatalogItemId, - ProductName = oi.ItemOrdered.ProductName, - UnitPrice = oi.UnitPrice, - Units = oi.Units - }).ToList(), - OrderNumber = o.Id, - ShippingAddress = o.ShipToAddress, - Total = o.Total() - }); - } - } } diff --git a/src/Web/Views/Order/MyOrders.cshtml b/src/Web/Views/Order/MyOrders.cshtml deleted file mode 100644 index 7478ba6..0000000 --- a/src/Web/Views/Order/MyOrders.cshtml +++ /dev/null @@ -1,38 +0,0 @@ -@model IEnumerable -@{ - ViewData["Title"] = "My Order History"; -} - -
-
-

@ViewData["Title"]

-
-
Order number
-
Date
-
Total
-
Status
-
-
- @if (Model != null && Model.Any()) - { - @foreach (var item in Model) - { -
-
@Html.DisplayFor(modelItem => item.OrderNumber)
-
@Html.DisplayFor(modelItem => item.OrderDate)
-
$ @Html.DisplayFor(modelItem => item.Total)
-
@Html.DisplayFor(modelItem => item.Status)
-
- Detail -
-
- @if (item.Status.ToLower() == "submitted") - { - Cancel - } -
-
- } - } -
-
diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index fa0efd1..477a2b9 100644 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -6,8 +6,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{227CF035-29B0-448D-97E4-944F9EA850E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{7C461394-ABDC-43CD-A798-71249C58BA67}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{7FED7440-2311-4D1E-958B-3E887C585CD2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{15EA4737-125B-4E6E-A806-E13B7EBCDCCF}" @@ -49,10 +47,6 @@ {227CF035-29B0-448D-97E4-944F9EA850E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {227CF035-29B0-448D-97E4-944F9EA850E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C461394-ABDC-43CD-A798-71249C58BA67}.Release|Any CPU.Build.0 = Release|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FED7440-2311-4D1E-958B-3E887C585CD2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,7 +85,6 @@ EndGlobalSection GlobalSection(NestedProjects) = preSolution {227CF035-29B0-448D-97E4-944F9EA850E5} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} - {7C461394-ABDC-43CD-A798-71249C58BA67} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {7FED7440-2311-4D1E-958B-3E887C585CD2} = {419A6ACE-0419-4315-A6FB-B0E63D39432E} {EF6877E6-59CB-43A7-8C2C-E70DD70CC5B6} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} {0F576306-7E2D-49B7-87B1-EB5D94CFD5FC} = {15EA4737-125B-4E6E-A806-E13B7EBCDCCF} diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index a121120..f1f77b2 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -1,20 +1,36 @@ - + net5.0 Microsoft.eShopWeb.ApplicationCore + true + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ApplicationCore/Data/CatalogContext.cs b/src/ApplicationCore/Data/CatalogContext.cs new file mode 100644 index 0000000..03cda58 --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContext.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using System.Reflection; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + + public class CatalogContext : DbContext + { + public CatalogContext(DbContextOptions options) : base(options) + { + } + + public DbSet Baskets { get; set; } + public DbSet CatalogItems { get; set; } + public DbSet CatalogBrands { get; set; } + public DbSet CatalogTypes { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } + public DbSet BasketItems { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } +} diff --git a/src/ApplicationCore/Data/CatalogContextSeed.cs b/src/ApplicationCore/Data/CatalogContextSeed.cs new file mode 100644 index 0000000..7f0722a --- /dev/null +++ b/src/ApplicationCore/Data/CatalogContextSeed.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class CatalogContextSeed + { + public static async Task SeedAsync(CatalogContext catalogContext, + ILoggerFactory loggerFactory, int? retry = 0) + { + int retryForAvailability = retry.Value; + try + { + // TODO: Only run this if using a real database + // catalogContext.Database.Migrate(); + if (!await catalogContext.CatalogBrands.AnyAsync()) + { + await catalogContext.CatalogBrands.AddRangeAsync( + GetPreconfiguredCatalogBrands()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogTypes.AnyAsync()) + { + await catalogContext.CatalogTypes.AddRangeAsync( + GetPreconfiguredCatalogTypes()); + + await catalogContext.SaveChangesAsync(); + } + + if (!await catalogContext.CatalogItems.AnyAsync()) + { + await catalogContext.CatalogItems.AddRangeAsync( + GetPreconfiguredItems()); + + await catalogContext.SaveChangesAsync(); + } + } + catch (Exception ex) + { + if (retryForAvailability < 10) + { + retryForAvailability++; + var log = loggerFactory.CreateLogger(); + log.LogError(ex.Message); + await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + } + throw; + } + } + + static IEnumerable GetPreconfiguredCatalogBrands() + { + return new List() + { + new CatalogBrand("Azure"), + new CatalogBrand(".NET"), + new CatalogBrand("Visual Studio"), + new CatalogBrand("SQL Server"), + new CatalogBrand("Other") + }; + } + + static IEnumerable GetPreconfiguredCatalogTypes() + { + return new List() + { + new CatalogType("Mug"), + new CatalogType("T-Shirt"), + new CatalogType("Sheet"), + new CatalogType("USB Memory Stick") + }; + } + + static IEnumerable GetPreconfiguredItems() + { + return new List() + { + new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), + new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), + new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), + new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), + new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), + new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), + new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), + new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), + new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), + new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), + new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), + new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") + }; + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketConfiguration.cs b/src/ApplicationCore/Data/Config/BasketConfiguration.cs new file mode 100644 index 0000000..fed7a73 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketConfiguration.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.Property(b => b.BuyerId) + .IsRequired() + .HasMaxLength(40); + } + } +} diff --git a/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs new file mode 100644 index 0000000..7d9aba1 --- /dev/null +++ b/src/ApplicationCore/Data/Config/BasketItemConfiguration.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class BasketItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.Property(bi => bi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs new file mode 100644 index 0000000..c571bb0 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogBrandConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogBrandConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_brand_hilo") + .IsRequired(); + + builder.Property(cb => cb.Brand) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs new file mode 100644 index 0000000..6a70a22 --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogItemConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Catalog"); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_hilo") + .IsRequired(); + + builder.Property(ci => ci.Name) + .IsRequired(true) + .HasMaxLength(50); + + builder.Property(ci => ci.Price) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + + builder.Property(ci => ci.PictureUri) + .IsRequired(false); + + builder.HasOne(ci => ci.CatalogBrand) + .WithMany() + .HasForeignKey(ci => ci.CatalogBrandId); + + builder.HasOne(ci => ci.CatalogType) + .WithMany() + .HasForeignKey(ci => ci.CatalogTypeId); + } + } +} diff --git a/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs new file mode 100644 index 0000000..5caa01c --- /dev/null +++ b/src/ApplicationCore/Data/Config/CatalogTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class CatalogTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(ci => ci.Id); + + builder.Property(ci => ci.Id) + .UseHiLo("catalog_type_hilo") + .IsRequired(); + + builder.Property(cb => cb.Type) + .IsRequired() + .HasMaxLength(100); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderConfiguration.cs b/src/ApplicationCore/Data/Config/OrderConfiguration.cs new file mode 100644 index 0000000..ddc9836 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); + + navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + + builder.OwnsOne(o => o.ShipToAddress, a => + { + a.WithOwner(); + + a.Property(a => a.ZipCode) + .HasMaxLength(18) + .IsRequired(); + + a.Property(a => a.Street) + .HasMaxLength(180) + .IsRequired(); + + a.Property(a => a.State) + .HasMaxLength(60); + + a.Property(a => a.Country) + .HasMaxLength(90) + .IsRequired(); + + a.Property(a => a.City) + .HasMaxLength(100) + .IsRequired(); + }); + } + } +} diff --git a/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs new file mode 100644 index 0000000..41a92e9 --- /dev/null +++ b/src/ApplicationCore/Data/Config/OrderItemConfiguration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Config +{ + public class OrderItemConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.OwnsOne(i => i.ItemOrdered, io => + { + io.WithOwner(); + + io.Property(cio => cio.ProductName) + .HasMaxLength(50) + .IsRequired(); + }); + + builder.Property(oi => oi.UnitPrice) + .IsRequired(true) + .HasColumnType("decimal(18,2)"); + } + } +} diff --git a/src/ApplicationCore/Data/EfRepository.cs b/src/ApplicationCore/Data/EfRepository.cs new file mode 100644 index 0000000..bd1fdee --- /dev/null +++ b/src/ApplicationCore/Data/EfRepository.cs @@ -0,0 +1,88 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + /// + /// "There's some repetition here - couldn't we have some the sync methods call the async?" + /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ + /// + /// + public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot + { + protected readonly CatalogContext _dbContext; + + public EfRepository(CatalogContext dbContext) + { + _dbContext = dbContext; + } + + public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + var keyValues = new object[] { id }; + return await _dbContext.Set().FindAsync(keyValues, cancellationToken); + } + + public async Task> ListAllAsync(CancellationToken cancellationToken = default) + { + return await _dbContext.Set().ToListAsync(cancellationToken); + } + + public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.ToListAsync(cancellationToken); + } + + public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.CountAsync(cancellationToken); + } + + public async Task AddAsync(T entity, CancellationToken cancellationToken = default) + { + await _dbContext.Set().AddAsync(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + + return entity; + } + + public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Entry(entity).State = EntityState.Modified; + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) + { + _dbContext.Set().Remove(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstAsync(cancellationToken); + } + + public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) + { + var specificationResult = ApplySpecification(spec); + return await specificationResult.FirstOrDefaultAsync(cancellationToken); + } + + private IQueryable ApplySpecification(ISpecification spec) + { + var evaluator = new SpecificationEvaluator(); + return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/FileItem.cs b/src/ApplicationCore/Data/FileItem.cs new file mode 100644 index 0000000..3267d24 --- /dev/null +++ b/src/ApplicationCore/Data/FileItem.cs @@ -0,0 +1,12 @@ +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class FileItem + { + public string FileName { get; set; } + public string Url { get; set; } + public long Size { get; set; } + public string Ext { get; set; } + public string Type { get; set; } + public string DataBase64 { get; set; } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs new file mode 100644 index 0000000..2f65f2c --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.Designer.cs @@ -0,0 +1,306 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20201202111507_InitialModel")] + partial class InitialModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs new file mode 100644 index 0000000..a850d10 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/20201202111507_InitialModel.cs @@ -0,0 +1,207 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + public partial class InitialModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateSequence( + name: "catalog_brand_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_hilo", + incrementBy: 10); + + migrationBuilder.CreateSequence( + name: "catalog_type_hilo", + incrementBy: 10); + + migrationBuilder.CreateTable( + name: "Baskets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Baskets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogBrands", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogBrands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CatalogTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CatalogTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BuyerId = table.Column(type: "nvarchar(max)", nullable: true), + OrderDate = table.Column(type: "datetimeoffset", nullable: false), + ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), + ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), + ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), + ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BasketItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + CatalogItemId = table.Column(type: "int", nullable: false), + BasketId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BasketItems", x => x.Id); + table.ForeignKey( + name: "FK_BasketItems_Baskets_BasketId", + column: x => x.BasketId, + principalTable: "Baskets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Catalog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false), + PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + CatalogTypeId = table.Column(type: "int", nullable: false), + CatalogBrandId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Catalog", x => x.Id); + table.ForeignKey( + name: "FK_Catalog_CatalogBrands_CatalogBrandId", + column: x => x.CatalogBrandId, + principalTable: "CatalogBrands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Catalog_CatalogTypes_CatalogTypeId", + column: x => x.CatalogTypeId, + principalTable: "CatalogTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), + ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), + UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), + Units = table.Column(type: "int", nullable: false), + OrderId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => x.Id); + table.ForeignKey( + name: "FK_OrderItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BasketItems_BasketId", + table: "BasketItems", + column: "BasketId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogBrandId", + table: "Catalog", + column: "CatalogBrandId"); + + migrationBuilder.CreateIndex( + name: "IX_Catalog_CatalogTypeId", + table: "Catalog", + column: "CatalogTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItems_OrderId", + table: "OrderItems", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BasketItems"); + + migrationBuilder.DropTable( + name: "Catalog"); + + migrationBuilder.DropTable( + name: "OrderItems"); + + migrationBuilder.DropTable( + name: "Baskets"); + + migrationBuilder.DropTable( + name: "CatalogBrands"); + + migrationBuilder.DropTable( + name: "CatalogTypes"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropSequence( + name: "catalog_brand_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_hilo"); + + migrationBuilder.DropSequence( + name: "catalog_type_hilo"); + } + } +} diff --git a/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs new file mode 100644 index 0000000..a1bd399 --- /dev/null +++ b/src/ApplicationCore/Data/Migrations/CatalogContextModelSnapshot.cs @@ -0,0 +1,304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations +{ + [DbContext(typeof(CatalogContext))] + partial class CatalogContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.HasSequence("catalog_brand_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_hilo") + .IncrementsBy(10); + + modelBuilder.HasSequence("catalog_type_hilo") + .IncrementsBy(10); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.ToTable("Baskets"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BasketId") + .HasColumnType("int"); + + b.Property("CatalogItemId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("BasketId"); + + b.ToTable("BasketItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_brand_hilo"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogBrands"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_hilo"); + + b.Property("CatalogBrandId") + .HasColumnType("int"); + + b.Property("CatalogTypeId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseHiLo("catalog_type_hilo"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("CatalogTypes"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("BuyerId") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderDate") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Units") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) + .WithMany("Items") + .HasForeignKey("BasketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CatalogBrand"); + + b.Navigation("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => + { + b1.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(90) + .HasColumnType("nvarchar(90)"); + + b1.Property("State") + .HasMaxLength(60) + .HasColumnType("nvarchar(60)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("nvarchar(180)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(18) + .HasColumnType("nvarchar(18)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("ShipToAddress"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => + { + b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) + .WithMany("OrderItems") + .HasForeignKey("OrderId"); + + b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => + { + b1.Property("OrderItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b1.Property("CatalogItemId") + .HasColumnType("int"); + + b1.Property("PictureUri") + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b1.HasKey("OrderItemId"); + + b1.ToTable("OrderItems"); + + b1.WithOwner() + .HasForeignKey("OrderItemId"); + }); + + b.Navigation("ItemOrdered"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => + { + b.Navigation("OrderItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Data/OrderRepository.cs b/src/ApplicationCore/Data/OrderRepository.cs new file mode 100644 index 0000000..57c3226 --- /dev/null +++ b/src/ApplicationCore/Data/OrderRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Data +{ + public class OrderRepository : EfRepository, IOrderRepository + { + public OrderRepository(CatalogContext dbContext) : base(dbContext) + { + } + + public Task GetByIdWithItemsAsync(int id) + { + return _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(i => i.ItemOrdered) + .FirstOrDefaultAsync(x => x.Id == id); + } + } +} diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cs b/src/ApplicationCore/Features/Orders/GetMyOrders.cs new file mode 100644 index 0000000..395b764 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Infrastructure.Data; + +namespace Microsoft.eShopWeb.Features.Orders +{ + [Route("order/my-orders")] + public class GetMyOrdersController : Controller + { + private readonly IMediator _mediator; + + public GetMyOrdersController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task MyOrders() + { + var viewModel = await _mediator.Send(new GetMyOrdersQuery(User.Identity.Name)); + + return View("/Features/Orders/GetMyOrders.cshtml", viewModel); + } + } + + public class GetMyOrdersViewModel + { + public IEnumerable Orders { get; set; } + } + + public class OrderSummaryViewModel + { + private const string DEFAULT_STATUS = "Pending"; + + public int OrderNumber { get; set; } + public DateTimeOffset OrderDate { get; set; } + public decimal Total { get; set; } + public string Status => DEFAULT_STATUS; + } + + public class GetMyOrdersQuery : IRequest + { + public string UserName { get; set; } + + public GetMyOrdersQuery(string userName) + { + UserName = userName; + } + } + + public class GetMyOrdersHandler : IRequestHandler + { + private readonly CatalogContext _db; + + public GetMyOrdersHandler(CatalogContext db) + { + _db = db; + } + + public async Task Handle(GetMyOrdersQuery request, CancellationToken cancellationToken) + { + var result = new GetMyOrdersViewModel(); + result.Orders = await _db.Orders + .Include(x => x.OrderItems) + .Where(x => x.BuyerId == request.UserName) + .Select(o => new OrderSummaryViewModel + { + OrderDate = o.OrderDate, + OrderNumber = o.Id, + Total = o.OrderItems.Sum(x => x.Units * x.UnitPrice), + }) + .ToArrayAsync(cancellationToken: cancellationToken); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml new file mode 100644 index 0000000..4ce3af5 --- /dev/null +++ b/src/ApplicationCore/Features/Orders/GetMyOrders.cshtml @@ -0,0 +1,40 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@model Microsoft.eShopWeb.Features.Orders.GetMyOrdersViewModel +@{ + ViewData["Title"] = "My Order History"; + Layout = "/Views/Shared/_Layout.cshtml"; +} + +
+
+

@ViewData["Title"]

+
+
Order number
+
Date
+
Total
+
Status
+
+
+ @if (Model != null && Model.Orders != null && Model.Orders.Any()) + { + @foreach (var item in Model.Orders) + { +
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@Html.DisplayFor(modelItem => item.OrderDate)
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ Detail +
+
+ @if (item.Status.ToLower() == "submitted") + { + Cancel + } +
+
+ } + } +
+
diff --git a/src/ApplicationCore/Identity/AppIdentityDbContext.cs b/src/ApplicationCore/Identity/AppIdentityDbContext.cs new file mode 100644 index 0000000..3fa8f62 --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContext.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class AppIdentityDbContext : IdentityDbContext + { + public AppIdentityDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); + } + } + +} diff --git a/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs new file mode 100644 index 0000000..e30edcd --- /dev/null +++ b/src/ApplicationCore/Identity/AppIdentityDbContextSeed.cs @@ -0,0 +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 userManager, RoleManager roleManager) + { + await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); + + var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; + 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } + } +} diff --git a/src/ApplicationCore/Identity/ApplicationUser.cs b/src/ApplicationCore/Identity/ApplicationUser.cs new file mode 100644 index 0000000..9b87155 --- /dev/null +++ b/src/ApplicationCore/Identity/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ApplicationCore/Identity/IdentityTokenClaimService.cs b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs new file mode 100644 index 0000000..f251803 --- /dev/null +++ b/src/ApplicationCore/Identity/IdentityTokenClaimService.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Identity +{ + public class IdentityTokenClaimService : ITokenClaimsService + { + private readonly UserManager _userManager; + + public IdentityTokenClaimService(UserManager userManager) + { + _userManager = userManager; + } + + public async Task GetTokenAsync(string userName) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); + var user = await _userManager.FindByNameAsync(userName); + var roles = await _userManager.GetRolesAsync(user); + var claims = new List { new Claim(ClaimTypes.Name, userName) }; + + foreach(var role in roles) + { + claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(claims.ToArray()), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs new file mode 100644 index 0000000..89ddc99 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + [Migration("20201202111612_InitialIdentityModel")] + partial class InitialIdentityModel + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs new file mode 100644 index 0000000..1126ed1 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/20201202111612_InitialIdentityModel.cs @@ -0,0 +1,219 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + public partial class InitialIdentityModel : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..9582d72 --- /dev/null +++ b/src/ApplicationCore/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs @@ -0,0 +1,271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.eShopWeb.Infrastructure.Identity; + +namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations +{ + [DbContext(typeof(AppIdentityDbContext))] + partial class AppIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .UseIdentityColumn(); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ApplicationCore/Logging/LoggerAdapter.cs b/src/ApplicationCore/Logging/LoggerAdapter.cs new file mode 100644 index 0000000..aea77e6 --- /dev/null +++ b/src/ApplicationCore/Logging/LoggerAdapter.cs @@ -0,0 +1,24 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.eShopWeb.Infrastructure.Logging +{ + public class LoggerAdapter : IAppLogger + { + private readonly ILogger _logger; + public LoggerAdapter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void LogWarning(string message, params object[] args) + { + _logger.LogWarning(message, args); + } + + public void LogInformation(string message, params object[] args) + { + _logger.LogInformation(message, args); + } + } +} diff --git a/src/ApplicationCore/Services/EmailSender.cs b/src/ApplicationCore/Services/EmailSender.cs new file mode 100644 index 0000000..c6850c7 --- /dev/null +++ b/src/ApplicationCore/Services/EmailSender.cs @@ -0,0 +1,16 @@ +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using System.Threading.Tasks; + +namespace Microsoft.eShopWeb.Infrastructure.Services +{ + // This class is used by the application to send email for account confirmation and password reset. + // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 + public class EmailSender : IEmailSender + { + public Task SendEmailAsync(string email, string subject, string message) + { + // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. + return Task.CompletedTask; + } + } +} diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs deleted file mode 100644 index 03cda58..0000000 --- a/src/Infrastructure/Data/CatalogContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using System.Reflection; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - - public class CatalogContext : DbContext - { - public CatalogContext(DbContextOptions options) : base(options) - { - } - - public DbSet Baskets { get; set; } - public DbSet CatalogItems { get; set; } - public DbSet CatalogBrands { get; set; } - public DbSet CatalogTypes { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } - public DbSet BasketItems { get; set; } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - } - } -} diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs deleted file mode 100644 index 7f0722a..0000000 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class CatalogContextSeed - { - public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int? retry = 0) - { - int retryForAvailability = retry.Value; - try - { - // TODO: Only run this if using a real database - // catalogContext.Database.Migrate(); - if (!await catalogContext.CatalogBrands.AnyAsync()) - { - await catalogContext.CatalogBrands.AddRangeAsync( - GetPreconfiguredCatalogBrands()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogTypes.AnyAsync()) - { - await catalogContext.CatalogTypes.AddRangeAsync( - GetPreconfiguredCatalogTypes()); - - await catalogContext.SaveChangesAsync(); - } - - if (!await catalogContext.CatalogItems.AnyAsync()) - { - await catalogContext.CatalogItems.AddRangeAsync( - GetPreconfiguredItems()); - - await catalogContext.SaveChangesAsync(); - } - } - catch (Exception ex) - { - if (retryForAvailability < 10) - { - retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); - } - throw; - } - } - - static IEnumerable GetPreconfiguredCatalogBrands() - { - return new List() - { - new CatalogBrand("Azure"), - new CatalogBrand(".NET"), - new CatalogBrand("Visual Studio"), - new CatalogBrand("SQL Server"), - new CatalogBrand("Other") - }; - } - - static IEnumerable GetPreconfiguredCatalogTypes() - { - return new List() - { - new CatalogType("Mug"), - new CatalogType("T-Shirt"), - new CatalogType("Sheet"), - new CatalogType("USB Memory Stick") - }; - } - - static IEnumerable GetPreconfiguredItems() - { - return new List() - { - new CatalogItem(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), - new CatalogItem(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), - new CatalogItem(2,5, "Prism White T-Shirt", "Prism White T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/3.png"), - new CatalogItem(2,2, ".NET Foundation Sweatshirt", ".NET Foundation Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/4.png"), - new CatalogItem(3,5, "Roslyn Red Sheet", "Roslyn Red Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/5.png"), - new CatalogItem(2,2, ".NET Blue Sweatshirt", ".NET Blue Sweatshirt", 12, "http://catalogbaseurltobereplaced/images/products/6.png"), - new CatalogItem(2,5, "Roslyn Red T-Shirt", "Roslyn Red T-Shirt", 12, "http://catalogbaseurltobereplaced/images/products/7.png"), - new CatalogItem(2,5, "Kudu Purple Sweatshirt", "Kudu Purple Sweatshirt", 8.5M, "http://catalogbaseurltobereplaced/images/products/8.png"), - new CatalogItem(1,5, "Cup White Mug", "Cup White Mug", 12, "http://catalogbaseurltobereplaced/images/products/9.png"), - new CatalogItem(3,2, ".NET Foundation Sheet", ".NET Foundation Sheet", 12, "http://catalogbaseurltobereplaced/images/products/10.png"), - new CatalogItem(3,2, "Cup Sheet", "Cup Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), - new CatalogItem(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") - }; - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs deleted file mode 100644 index fed7a73..0000000 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.Property(b => b.BuyerId) - .IsRequired() - .HasMaxLength(40); - } - } -} diff --git a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs b/src/Infrastructure/Data/Config/BasketItemConfiguration.cs deleted file mode 100644 index 7d9aba1..0000000 --- a/src/Infrastructure/Data/Config/BasketItemConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class BasketItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.Property(bi => bi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs b/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs deleted file mode 100644 index c571bb0..0000000 --- a/src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogBrandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_brand_hilo") - .IsRequired(); - - builder.Property(cb => cb.Brand) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs b/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs deleted file mode 100644 index 6a70a22..0000000 --- a/src/Infrastructure/Data/Config/CatalogItemConfiguration.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("Catalog"); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_hilo") - .IsRequired(); - - builder.Property(ci => ci.Name) - .IsRequired(true) - .HasMaxLength(50); - - builder.Property(ci => ci.Price) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - - builder.Property(ci => ci.PictureUri) - .IsRequired(false); - - builder.HasOne(ci => ci.CatalogBrand) - .WithMany() - .HasForeignKey(ci => ci.CatalogBrandId); - - builder.HasOne(ci => ci.CatalogType) - .WithMany() - .HasForeignKey(ci => ci.CatalogTypeId); - } - } -} diff --git a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs b/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs deleted file mode 100644 index 5caa01c..0000000 --- a/src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class CatalogTypeConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(ci => ci.Id); - - builder.Property(ci => ci.Id) - .UseHiLo("catalog_type_hilo") - .IsRequired(); - - builder.Property(cb => cb.Type) - .IsRequired() - .HasMaxLength(100); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs deleted file mode 100644 index ddc9836..0000000 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); - - builder.OwnsOne(o => o.ShipToAddress, a => - { - a.WithOwner(); - - a.Property(a => a.ZipCode) - .HasMaxLength(18) - .IsRequired(); - - a.Property(a => a.Street) - .HasMaxLength(180) - .IsRequired(); - - a.Property(a => a.State) - .HasMaxLength(60); - - a.Property(a => a.Country) - .HasMaxLength(90) - .IsRequired(); - - a.Property(a => a.City) - .HasMaxLength(100) - .IsRequired(); - }); - } - } -} diff --git a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs b/src/Infrastructure/Data/Config/OrderItemConfiguration.cs deleted file mode 100644 index 41a92e9..0000000 --- a/src/Infrastructure/Data/Config/OrderItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Config -{ - public class OrderItemConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.OwnsOne(i => i.ItemOrdered, io => - { - io.WithOwner(); - - io.Property(cio => cio.ProductName) - .HasMaxLength(50) - .IsRequired(); - }); - - builder.Property(oi => oi.UnitPrice) - .IsRequired(true) - .HasColumnType("decimal(18,2)"); - } - } -} diff --git a/src/Infrastructure/Data/EfRepository.cs b/src/Infrastructure/Data/EfRepository.cs deleted file mode 100644 index bd1fdee..0000000 --- a/src/Infrastructure/Data/EfRepository.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ardalis.Specification; -using Ardalis.Specification.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - /// - /// "There's some repetition here - couldn't we have some the sync methods call the async?" - /// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/ - /// - /// - public class EfRepository : IAsyncRepository where T : BaseEntity, IAggregateRoot - { - protected readonly CatalogContext _dbContext; - - public EfRepository(CatalogContext dbContext) - { - _dbContext = dbContext; - } - - public virtual async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - var keyValues = new object[] { id }; - return await _dbContext.Set().FindAsync(keyValues, cancellationToken); - } - - public async Task> ListAllAsync(CancellationToken cancellationToken = default) - { - return await _dbContext.Set().ToListAsync(cancellationToken); - } - - public async Task> ListAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.ToListAsync(cancellationToken); - } - - public async Task CountAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.CountAsync(cancellationToken); - } - - public async Task AddAsync(T entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - - return entity; - } - - public async Task UpdateAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task DeleteAsync(T entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken); - } - - public async Task FirstAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstAsync(cancellationToken); - } - - public async Task FirstOrDefaultAsync(ISpecification spec, CancellationToken cancellationToken = default) - { - var specificationResult = ApplySpecification(spec); - return await specificationResult.FirstOrDefaultAsync(cancellationToken); - } - - private IQueryable ApplySpecification(ISpecification spec) - { - var evaluator = new SpecificationEvaluator(); - return evaluator.GetQuery(_dbContext.Set().AsQueryable(), spec); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs deleted file mode 100644 index 3267d24..0000000 --- a/src/Infrastructure/Data/FileItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class FileItem - { - public string FileName { get; set; } - public string Url { get; set; } - public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } - } -} \ No newline at end of file diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs deleted file mode 100644 index 2f65f2c..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.Designer.cs +++ /dev/null @@ -1,306 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - [Migration("20201202111507_InitialModel")] - partial class InitialModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs b/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs deleted file mode 100644 index a850d10..0000000 --- a/src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - public partial class InitialModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateSequence( - name: "catalog_brand_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_hilo", - incrementBy: 10); - - migrationBuilder.CreateSequence( - name: "catalog_type_hilo", - incrementBy: 10); - - migrationBuilder.CreateTable( - name: "Baskets", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Baskets", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogBrands", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Brand = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogBrands", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "CatalogTypes", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Type = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CatalogTypes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - BuyerId = table.Column(type: "nvarchar(max)", nullable: true), - OrderDate = table.Column(type: "datetimeoffset", nullable: false), - ShipToAddress_Street = table.Column(type: "nvarchar(180)", maxLength: 180, nullable: true), - ShipToAddress_City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), - ShipToAddress_State = table.Column(type: "nvarchar(60)", maxLength: 60, nullable: true), - ShipToAddress_Country = table.Column(type: "nvarchar(90)", maxLength: 90, nullable: true), - ShipToAddress_ZipCode = table.Column(type: "nvarchar(18)", maxLength: 18, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "BasketItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Quantity = table.Column(type: "int", nullable: false), - CatalogItemId = table.Column(type: "int", nullable: false), - BasketId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BasketItems", x => x.Id); - table.ForeignKey( - name: "FK_BasketItems_Baskets_BasketId", - column: x => x.BasketId, - principalTable: "Baskets", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Catalog", - columns: table => new - { - Id = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: true), - Price = table.Column(type: "decimal(18,2)", nullable: false), - PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - CatalogTypeId = table.Column(type: "int", nullable: false), - CatalogBrandId = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Catalog", x => x.Id); - table.ForeignKey( - name: "FK_Catalog_CatalogBrands_CatalogBrandId", - column: x => x.CatalogBrandId, - principalTable: "CatalogBrands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Catalog_CatalogTypes_CatalogTypeId", - column: x => x.CatalogTypeId, - principalTable: "CatalogTypes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ItemOrdered_CatalogItemId = table.Column(type: "int", nullable: true), - ItemOrdered_ProductName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ItemOrdered_PictureUri = table.Column(type: "nvarchar(max)", nullable: true), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - Units = table.Column(type: "int", nullable: false), - OrderId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_BasketItems_BasketId", - table: "BasketItems", - column: "BasketId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogBrandId", - table: "Catalog", - column: "CatalogBrandId"); - - migrationBuilder.CreateIndex( - name: "IX_Catalog_CatalogTypeId", - table: "Catalog", - column: "CatalogTypeId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItems_OrderId", - table: "OrderItems", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BasketItems"); - - migrationBuilder.DropTable( - name: "Catalog"); - - migrationBuilder.DropTable( - name: "OrderItems"); - - migrationBuilder.DropTable( - name: "Baskets"); - - migrationBuilder.DropTable( - name: "CatalogBrands"); - - migrationBuilder.DropTable( - name: "CatalogTypes"); - - migrationBuilder.DropTable( - name: "Orders"); - - migrationBuilder.DropSequence( - name: "catalog_brand_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_hilo"); - - migrationBuilder.DropSequence( - name: "catalog_type_hilo"); - } - } -} diff --git a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs deleted file mode 100644 index a1bd399..0000000 --- a/src/Infrastructure/Data/Migrations/CatalogContextModelSnapshot.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Data; - -namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations -{ - [DbContext(typeof(CatalogContext))] - partial class CatalogContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.HasSequence("catalog_brand_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_hilo") - .IncrementsBy(10); - - modelBuilder.HasSequence("catalog_type_hilo") - .IncrementsBy(10); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)"); - - b.HasKey("Id"); - - b.ToTable("Baskets"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BasketId") - .HasColumnType("int"); - - b.Property("CatalogItemId") - .HasColumnType("int"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("BasketId"); - - b.ToTable("BasketItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_brand_hilo"); - - b.Property("Brand") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogBrands"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_hilo"); - - b.Property("CatalogBrandId") - .HasColumnType("int"); - - b.Property("CatalogTypeId") - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b.Property("Price") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("CatalogBrandId"); - - b.HasIndex("CatalogTypeId"); - - b.ToTable("Catalog"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseHiLo("catalog_type_hilo"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("CatalogTypes"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("BuyerId") - .HasColumnType("nvarchar(max)"); - - b.Property("OrderDate") - .HasColumnType("datetimeoffset"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("OrderId") - .HasColumnType("int"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("Units") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrderId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.BasketItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", null) - .WithMany("Items") - .HasForeignKey("BasketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogBrand", "CatalogBrand") - .WithMany() - .HasForeignKey("CatalogBrandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.CatalogType", "CatalogType") - .WithMany() - .HasForeignKey("CatalogTypeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CatalogBrand"); - - b.Navigation("CatalogType"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Address", "ShipToAddress", b1 => - { - b1.Property("OrderId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b1.Property("Country") - .IsRequired() - .HasMaxLength(90) - .HasColumnType("nvarchar(90)"); - - b1.Property("State") - .HasMaxLength(60) - .HasColumnType("nvarchar(60)"); - - b1.Property("Street") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("nvarchar(180)"); - - b1.Property("ZipCode") - .IsRequired() - .HasMaxLength(18) - .HasColumnType("nvarchar(18)"); - - b1.HasKey("OrderId"); - - b1.ToTable("Orders"); - - b1.WithOwner() - .HasForeignKey("OrderId"); - }); - - b.Navigation("ShipToAddress"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.OrderItem", b => - { - b.HasOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", null) - .WithMany("OrderItems") - .HasForeignKey("OrderId"); - - b.OwnsOne("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.CatalogItemOrdered", "ItemOrdered", b1 => - { - b1.Property("OrderItemId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b1.Property("CatalogItemId") - .HasColumnType("int"); - - b1.Property("PictureUri") - .HasColumnType("nvarchar(max)"); - - b1.Property("ProductName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b1.HasKey("OrderItemId"); - - b1.ToTable("OrderItems"); - - b1.WithOwner() - .HasForeignKey("OrderItemId"); - }); - - b.Navigation("ItemOrdered"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate.Basket", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate.Order", b => - { - b.Navigation("OrderItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Data/OrderRepository.cs b/src/Infrastructure/Data/OrderRepository.cs deleted file mode 100644 index 57c3226..0000000 --- a/src/Infrastructure/Data/OrderRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Data -{ - public class OrderRepository : EfRepository, IOrderRepository - { - public OrderRepository(CatalogContext dbContext) : base(dbContext) - { - } - - public Task GetByIdWithItemsAsync(int id) - { - return _dbContext.Orders - .Include(o => o.OrderItems) - .ThenInclude(i => i.ItemOrdered) - .FirstOrDefaultAsync(x => x.Id == id); - } - } -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContext.cs b/src/Infrastructure/Identity/AppIdentityDbContext.cs deleted file mode 100644 index 3fa8f62..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class AppIdentityDbContext : IdentityDbContext - { - public AppIdentityDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } - -} diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs deleted file mode 100644 index e30edcd..0000000 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 userManager, RoleManager roleManager) - { - await roleManager.CreateAsync(new IdentityRole(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)); - - var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; - 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, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); - } - } -} diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs deleted file mode 100644 index 9b87155..0000000 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class ApplicationUser : IdentityUser - { - } -} diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs deleted file mode 100644 index f251803..0000000 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Identity -{ - public class IdentityTokenClaimService : ITokenClaimsService - { - private readonly UserManager _userManager; - - public IdentityTokenClaimService(UserManager userManager) - { - _userManager = userManager; - } - - public async Task GetTokenAsync(string userName) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - var user = await _userManager.FindByNameAsync(userName); - var roles = await _userManager.GetRolesAsync(user); - var claims = new List { new Claim(ClaimTypes.Name, userName) }; - - foreach(var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role)); - } - - var tokenDescriptor = new SecurityTokenDescriptor - { - Subject = new ClaimsIdentity(claims.ToArray()), - Expires = DateTime.UtcNow.AddDays(7), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) - }; - var token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs deleted file mode 100644 index 89ddc99..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.Designer.cs +++ /dev/null @@ -1,273 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - [Migration("20201202111612_InitialIdentityModel")] - partial class InitialIdentityModel - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs b/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs deleted file mode 100644 index 1126ed1..0000000 --- a/src/Infrastructure/Identity/Migrations/20201202111612_InitialIdentityModel.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - public partial class InitialIdentityModel : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs b/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs deleted file mode 100644 index 9582d72..0000000 --- a/src/Infrastructure/Identity/Migrations/AppIdentityDbContextModelSnapshot.cs +++ /dev/null @@ -1,271 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.eShopWeb.Infrastructure.Identity; - -namespace Microsoft.eShopWeb.Infrastructure.Identity.Migrations -{ - [DbContext(typeof(AppIdentityDbContext))] - partial class AppIdentityDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .UseIdentityColumn(); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.eShopWeb.Infrastructure.Identity.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index ce18e63..a6f8703 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -17,6 +17,6 @@ - +
diff --git a/src/Infrastructure/Logging/LoggerAdapter.cs b/src/Infrastructure/Logging/LoggerAdapter.cs deleted file mode 100644 index aea77e6..0000000 --- a/src/Infrastructure/Logging/LoggerAdapter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.Extensions.Logging; - -namespace Microsoft.eShopWeb.Infrastructure.Logging -{ - public class LoggerAdapter : IAppLogger - { - private readonly ILogger _logger; - public LoggerAdapter(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - public void LogWarning(string message, params object[] args) - { - _logger.LogWarning(message, args); - } - - public void LogInformation(string message, params object[] args) - { - _logger.LogInformation(message, args); - } - } -} diff --git a/src/Infrastructure/Services/EmailSender.cs b/src/Infrastructure/Services/EmailSender.cs deleted file mode 100644 index c6850c7..0000000 --- a/src/Infrastructure/Services/EmailSender.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.Infrastructure.Services -{ - // This class is used by the application to send email for account confirmation and password reset. - // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 - public class EmailSender : IEmailSender - { - public Task SendEmailAsync(string email, string subject, string message) - { - // TODO: Wire this up to actual email sending logic via SendGrid, local SMTP, etc. - return Task.CompletedTask; - } - } -} diff --git a/src/Web/Configuration/ConfigureWebServices.cs b/src/Web/Configuration/ConfigureWebServices.cs index 87030d5..4b869e4 100644 --- a/src/Web/Configuration/ConfigureWebServices.cs +++ b/src/Web/Configuration/ConfigureWebServices.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.eShopWeb.Features.Orders; using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.eShopWeb.Web.Services; using Microsoft.Extensions.Configuration; @@ -10,7 +11,7 @@ { public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration) { - services.AddMediatR(typeof(BasketViewModelService).Assembly); + services.AddMediatR(typeof(BasketViewModelService).Assembly, typeof(GetMyOrdersHandler).Assembly); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index aa5b36a..d0bf2cb 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -19,14 +19,6 @@ _mediator = mediator; } - [HttpGet] - public async Task MyOrders() - { - var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); - - return View(viewModel); - } - [HttpGet("{orderId}")] public async Task Detail(int orderId) { diff --git a/src/Web/Features/MyOrders/GetMyOrders.cs b/src/Web/Features/MyOrders/GetMyOrders.cs deleted file mode 100644 index b99a9da..0000000 --- a/src/Web/Features/MyOrders/GetMyOrders.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; -using Microsoft.eShopWeb.Web.ViewModels; -using System.Collections.Generic; - -namespace Microsoft.eShopWeb.Web.Features.MyOrders -{ - public class GetMyOrders : IRequest> - { - public string UserName { get; set; } - - public GetMyOrders(string userName) - { - UserName = userName; - } - } -} diff --git a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs index e66c38a..3d6c5e4 100644 --- a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs +++ b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs @@ -9,35 +9,5 @@ namespace Microsoft.eShopWeb.Web.Features.MyOrders { - public class GetMyOrdersHandler : IRequestHandler> - { - private readonly IOrderRepository _orderRepository; - public GetMyOrdersHandler(IOrderRepository orderRepository) - { - _orderRepository = orderRepository; - } - - public async Task> Handle(GetMyOrders request, CancellationToken cancellationToken) - { - var specification = new CustomerOrdersWithItemsSpecification(request.UserName); - var orders = await _orderRepository.ListAsync(specification, cancellationToken); - - return orders.Select(o => new OrderViewModel - { - OrderDate = o.OrderDate, - OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() - { - PictureUrl = oi.ItemOrdered.PictureUri, - ProductId = oi.ItemOrdered.CatalogItemId, - ProductName = oi.ItemOrdered.ProductName, - UnitPrice = oi.UnitPrice, - Units = oi.Units - }).ToList(), - OrderNumber = o.Id, - ShippingAddress = o.ShipToAddress, - Total = o.Total() - }); - } - } } diff --git a/src/Web/Views/Order/MyOrders.cshtml b/src/Web/Views/Order/MyOrders.cshtml deleted file mode 100644 index 7478ba6..0000000 --- a/src/Web/Views/Order/MyOrders.cshtml +++ /dev/null @@ -1,38 +0,0 @@ -@model IEnumerable -@{ - ViewData["Title"] = "My Order History"; -} - -
-
-

@ViewData["Title"]

-
-
Order number
-
Date
-
Total
-
Status
-
-
- @if (Model != null && Model.Any()) - { - @foreach (var item in Model) - { -
-
@Html.DisplayFor(modelItem => item.OrderNumber)
-
@Html.DisplayFor(modelItem => item.OrderDate)
-
$ @Html.DisplayFor(modelItem => item.Total)
-
@Html.DisplayFor(modelItem => item.Status)
-
- Detail -
-
- @if (item.Status.ToLower() == "submitted") - { - Cancel - } -
-
- } - } -
-
diff --git a/tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders.cs b/tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders.cs index 3ee3e5c..7da3b1c 100644 --- a/tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders.cs +++ b/tests/UnitTests/MediatorHandlers/OrdersTests/GetMyOrders.cs @@ -1,36 +1,24 @@ -using Ardalis.Specification; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.Web.Features.MyOrders; -using Moq; -using System.Collections.Generic; -using System.Threading; +using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb.Features.Orders; +using Microsoft.eShopWeb.Infrastructure.Data; using Xunit; namespace Microsoft.eShopWeb.UnitTests.MediatorHandlers.OrdersTests { public class GetMyOrders { - private readonly Mock _mockOrderRepository; - - public GetMyOrders() - { - var item = new OrderItem(new CatalogItemOrdered(1, "ProductName", "URI"), 10.00m, 10); - var address = new Address(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()); - Order order = new Order("buyerId", address, new List { item }); - - _mockOrderRepository = new Mock(); - _mockOrderRepository.Setup(x => x.ListAsync(It.IsAny>(),default)).ReturnsAsync(new List { order }); - } - [Fact] public async Task NotReturnNullIfOrdersArePresent() { - var request = new eShopWeb.Web.Features.MyOrders.GetMyOrders("SomeUserName"); + var request = new GetMyOrdersQuery("SomeUserName"); - var handler = new GetMyOrdersHandler(_mockOrderRepository.Object); + var options = new DbContextOptionsBuilder(). + UseInMemoryDatabase(databaseName: "Catalog") + .Options; + var handler = new GetMyOrdersHandler(new CatalogContext(options)); var result = await handler.Handle(request, CancellationToken.None); Assert.NotNull(result);