From b629c088524118f67e2401307def1de0d020b4c8 Mon Sep 17 00:00:00 2001 From: Mel Grubb Date: Sat, 23 Feb 2019 21:11:51 -0500 Subject: [PATCH] Filled out the services and controllers further. Most endpoints are usable now. --- README.md | 9 +- Solution.props | 3 +- Store.Common/Contracts/PagingOptions.cs | 14 ++ Store.Common/Store.Common.csproj | 3 +- Store.Domain/.Framework/Model.cs | 3 +- Store.Domain/.Framework/Repository.cs | 41 +++-- ...90224014713_Initial Migration.Designer.cs} | 168 +++++++++++++++++- ...cs => 20190224014713_Initial Migration.cs} | 145 ++++++++++----- .../Migrations/StoreContextModelSnapshot.cs | 166 ++++++++++++++++- Store.Domain/Models/Order.cs | 10 ++ Store.Domain/Models/OrderItem.cs | 3 +- Store.Domain/Models/OrderStatus.cs | 2 +- Store.Domain/Models/StoreContext.Seed.cs | 21 ++- Store.Domain/Models/StoreContext.cs | 15 ++ Store.Domain/Models/User.cs | 8 +- .../Repositories/AddressRepository.cs | 1 - Store.Domain/Repositories/OrderRepository.cs | 2 + .../Repositories/ProductRepository.cs | 1 + Store.Domain/Store.Domain.csproj | 4 + Store.Services/.Framework/LookupDto.cs | 14 ++ .../Contracts/Category/CategoryDto.cs | 2 +- Store.Services/Contracts/Order/OrderDto.cs | 12 +- .../Contracts/Order/OrderItemDto.cs | 6 +- .../Contracts/Order/OrderStatusDto.cs | 8 + .../Contracts/Product/ProductDto.cs | 14 ++ .../Contracts/Product/ProductStatusDto.cs | 8 + Store.Services/Contracts/User/UserDto.cs | 8 + Store.Services/Mapping/CategoryMapper.cs | 9 + Store.Services/Mapping/Mapper.cs | 45 +++++ Store.Services/Mapping/OrderMapper.cs | 9 + Store.Services/Mapping/ProductDtoMapper.cs | 9 + Store.Services/Mapping/ProductMapper.cs | 9 + Store.Services/Services/AddressService.cs | 29 +-- Store.Services/Services/CategoryService.cs | 73 +++++++- Store.Services/Services/OrderService.cs | 73 +++++++- Store.Services/Services/ProductService.cs | 73 +++++++- Store.Services/Store.Services.csproj | 3 +- Store.Services/Store.Services.xml | 2 +- .../Store.Tests.Integration.csproj | 2 + Store.Web/.Framework/StoreMvcController.cs | 2 + .../Controllers/Api/AddressController.cs | 18 +- .../Controllers/Api/CategoryController.cs | 98 ++++++++++ Store.Web/Controllers/Api/OrderController.cs | 98 ++++++++++ .../Controllers/Api/ProductController.cs | 99 +++++++++++ Store.Web/Controllers/Mvc/HomeController.cs | 3 +- Store.Web/Controllers/Mvc/StatusController.cs | 47 +++-- Store.Web/Program.cs | 6 +- Store.Web/Properties/launchSettings.json | 12 +- Store.Web/Startup.cs | 9 +- Store.Web/Store.Web.csproj | 4 +- Store.Web/Store.Web.xml | 75 +++++++- Store.Web/Views/Home/Index.cshtml | 6 +- .../Views/Shared/_CookieConsentPartial.cshtml | 17 +- Store.Web/Views/Shared/_Layout.cshtml | 114 ++++++------ Store.Web/Views/Status/Index.cshtml | 2 +- Store.Web/appsettings.Development.json | 2 +- Store.Web/appsettings.json | 2 +- Store.sln | 27 ++- 58 files changed, 1439 insertions(+), 229 deletions(-) create mode 100644 Store.Common/Contracts/PagingOptions.cs rename Store.Domain/Migrations/{20190220030839_Initial Migration.Designer.cs => 20190224014713_Initial Migration.Designer.cs} (79%) rename Store.Domain/Migrations/{20190220030839_Initial Migration.cs => 20190224014713_Initial Migration.cs} (77%) create mode 100644 Store.Services/.Framework/LookupDto.cs create mode 100644 Store.Services/Contracts/Order/OrderStatusDto.cs create mode 100644 Store.Services/Contracts/Product/ProductDto.cs create mode 100644 Store.Services/Contracts/Product/ProductStatusDto.cs create mode 100644 Store.Services/Contracts/User/UserDto.cs create mode 100644 Store.Services/Mapping/CategoryMapper.cs create mode 100644 Store.Services/Mapping/Mapper.cs create mode 100644 Store.Services/Mapping/OrderMapper.cs create mode 100644 Store.Services/Mapping/ProductDtoMapper.cs create mode 100644 Store.Services/Mapping/ProductMapper.cs create mode 100644 Store.Web/Controllers/Api/CategoryController.cs create mode 100644 Store.Web/Controllers/Api/OrderController.cs create mode 100644 Store.Web/Controllers/Api/ProductController.cs diff --git a/README.md b/README.md index 6b13804..dff3ad0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # Code -This folder contains the source code for the sample solution. \ No newline at end of file +This folder contains the source code for the sample solution. + +## Branching Strategy + +The "master" branch has the solution itself, and the stubs for the test assemblies. +The "Mx" branches each demonstrate the different test data generation strategies. + +When changes are made to the store solution, they should be made in master and then merged to the other branches so that each strategy branch is running against the same basic solution. diff --git a/Solution.props b/Solution.props index ecedd47..0c482c2 100644 --- a/Solution.props +++ b/Solution.props @@ -1,5 +1,4 @@ - - + diff --git a/Store.Common/Contracts/PagingOptions.cs b/Store.Common/Contracts/PagingOptions.cs new file mode 100644 index 0000000..04a9c04 --- /dev/null +++ b/Store.Common/Contracts/PagingOptions.cs @@ -0,0 +1,14 @@ +namespace Store.Common.Contracts +{ + public class PagingOptions + { + private int _take; + public int Skip { get; set; } + + public int Take + { + get => _take == 0 ? 10 : _take; + set => _take = value; + } + } +} diff --git a/Store.Common/Store.Common.csproj b/Store.Common/Store.Common.csproj index f73352f..fdfc2a2 100644 --- a/Store.Common/Store.Common.csproj +++ b/Store.Common/Store.Common.csproj @@ -1,6 +1,6 @@  - + netcoreapp2.2 @@ -12,7 +12,6 @@ - diff --git a/Store.Domain/.Framework/Model.cs b/Store.Domain/.Framework/Model.cs index 3db1c19..cd4f328 100644 --- a/Store.Domain/.Framework/Model.cs +++ b/Store.Domain/.Framework/Model.cs @@ -15,7 +15,8 @@ public abstract class Model : IModel public bool IsNew => Id.Equals(0); /// The primary key / identity column. - [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } } } diff --git a/Store.Domain/.Framework/Repository.cs b/Store.Domain/.Framework/Repository.cs index 7b3df3e..a259d96 100644 --- a/Store.Domain/.Framework/Repository.cs +++ b/Store.Domain/.Framework/Repository.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Store.Common.Contracts; using Store.Domain.Models; namespace Store.Domain.Framework @@ -27,6 +28,8 @@ public interface IRepository : IRepository where T : IModel Task GetAsync(int userId, int id); + Task> GetAsync(int userId, PagingOptions pagingOptions); + Task SaveChangesAsync(int userId); Task SingleOrDefaultAsync(int userId, Expression> predicate = null); @@ -94,20 +97,6 @@ public virtual Task AnyAsync(Expression> predicate = null) return result; } - public virtual Task CountAsync(Expression> predicate = null) - { - IQueryable query = StoreContext.Set(); - - if (typeof(T) is ISoftDeleteable) - { - query = query.Where(x => !((ISoftDeleteable)x).DeletedUtc.HasValue); - } - - var result = query.CountAsync(predicate); - - return result; - } - public virtual async Task DeleteAsync(int userId, T model) { if (_isSoftDeleteable) @@ -138,6 +127,16 @@ public virtual Task GetAsync(int userId, int id) return results; } + public async Task> GetAsync(int userId, PagingOptions pagingOptions) + { + var results = await GetQuery(userId) + .Skip(pagingOptions.Skip) + .Take(pagingOptions.Take) + .ToListAsync(); + + return results; + } + public virtual Task SaveChangesAsync(int userId) { var result = StoreContext.SaveChangesAsync(userId); @@ -152,6 +151,20 @@ public virtual async Task SingleOrDefaultAsync(int userId, Expression CountAsync(Expression> predicate = null) + { + IQueryable query = StoreContext.Set(); + + if (typeof(T) is ISoftDeleteable) + { + query = query.Where(x => !((ISoftDeleteable)x).DeletedUtc.HasValue); + } + + var result = query.CountAsync(predicate); + + return result; + } + protected virtual IQueryable GetBaseQuery(int userId, Expression> predicate = null) { IQueryable query = StoreContext.Set(); diff --git a/Store.Domain/Migrations/20190220030839_Initial Migration.Designer.cs b/Store.Domain/Migrations/20190224014713_Initial Migration.Designer.cs similarity index 79% rename from Store.Domain/Migrations/20190220030839_Initial Migration.Designer.cs rename to Store.Domain/Migrations/20190224014713_Initial Migration.Designer.cs index abec692..c20ee98 100644 --- a/Store.Domain/Migrations/20190220030839_Initial Migration.Designer.cs +++ b/Store.Domain/Migrations/20190224014713_Initial Migration.Designer.cs @@ -10,7 +10,7 @@ namespace Store.Domain.Migrations { [DbContext(typeof(StoreContext))] - [Migration("20190220030839_Initial Migration")] + [Migration("20190224014713_Initial Migration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -64,8 +64,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) City = "Anytown", CreatedById = 1, CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - Line1 = "123 Any St.", - Line2 = "Suite 456", + Line1 = "Billing Dept.", + Line2 = "123 Any St.", + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + StateId = 1, + ZipCode = "12345" + }, + new + { + Id = 2, + City = "Anytown", + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Line1 = "Receiving Dept.", + Line2 = "123 Any St.", ModifiedById = 1, ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), StateId = 1, @@ -120,6 +133,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + b.Property("BillingAddressId"); + b.Property("CreatedById"); b.Property("CreatedUtc") @@ -132,12 +147,18 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrderStatusId"); + b.Property("ShippingAddressId"); + b.Property("UserId"); b.HasKey("Id"); + b.HasIndex("BillingAddressId"); + b.HasIndex("OrderStatusId"); + b.HasIndex("ShippingAddressId"); + b.HasIndex("UserId"); b.ToTable("Orders"); @@ -146,11 +167,49 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) new { Id = 1, + BillingAddressId = 1, CreatedById = 1, CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), ModifiedById = 1, ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), OrderStatusId = 4, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 2, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 3, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 3, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 2, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 4, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 1, + ShippingAddressId = 2, UserId = 1 }); }); @@ -200,6 +259,90 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) Price = 1.00m, ProductId = 1, Quantity = 1 + }, + new + { + Id = 2, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 1, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 3, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 2, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 4, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 2, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 5, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 3, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 6, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 3, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 7, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 4, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 8, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 4, + Price = 1.00m, + ProductId = 2, + Quantity = 1 }); }); @@ -717,6 +860,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + b.Property("AddressId"); + b.Property("CreatedById"); b.Property("CreatedUtc") @@ -745,6 +890,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("AddressId"); + b.ToTable("Users"); b.HasData( @@ -794,11 +941,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Store.Domain.Models.Order", b => { + b.HasOne("Store.Domain.Models.Address", "BillingAddress") + .WithMany() + .HasForeignKey("BillingAddressId"); + b.HasOne("Store.Domain.Models.OrderStatus", "OrderStatus") .WithMany() .HasForeignKey("OrderStatusId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Store.Domain.Models.Address", "ShippingAddress") + .WithMany() + .HasForeignKey("ShippingAddressId"); + b.HasOne("Store.Domain.Models.User", "User") .WithMany() .HasForeignKey("UserId") @@ -830,6 +985,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("ProductStatusId") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("Store.Domain.Models.User", b => + { + b.HasOne("Store.Domain.Models.Address", "Address") + .WithMany() + .HasForeignKey("AddressId"); + }); #pragma warning restore 612, 618 } } diff --git a/Store.Domain/Migrations/20190220030839_Initial Migration.cs b/Store.Domain/Migrations/20190224014713_Initial Migration.cs similarity index 77% rename from Store.Domain/Migrations/20190220030839_Initial Migration.cs rename to Store.Domain/Migrations/20190224014713_Initial Migration.cs index 52e1f7e..7b2b539 100644 --- a/Store.Domain/Migrations/20190220030839_Initial Migration.cs +++ b/Store.Domain/Migrations/20190224014713_Initial Migration.cs @@ -72,30 +72,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_States", x => x.Id); }); - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - CreatedById = table.Column(nullable: true), - CreatedUtc = table.Column(type: "DateTime2", nullable: false), - ModifiedById = table.Column(nullable: true), - ModifiedUtc = table.Column(type: "DateTime2", nullable: false), - DeletedById = table.Column(nullable: true), - DeletedUtc = table.Column(nullable: true), - FirstName = table.Column(nullable: true), - LastName = table.Column(nullable: true), - MiddleName = table.Column(nullable: true), - PasswordHash = table.Column(nullable: true), - PhoneNumber = table.Column(nullable: true), - UserName = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - migrationBuilder.CreateTable( name: "Products", columns: table => new @@ -160,6 +136,37 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + CreatedById = table.Column(nullable: true), + CreatedUtc = table.Column(type: "DateTime2", nullable: false), + ModifiedById = table.Column(nullable: true), + ModifiedUtc = table.Column(type: "DateTime2", nullable: false), + DeletedById = table.Column(nullable: true), + DeletedUtc = table.Column(nullable: true), + AddressId = table.Column(nullable: true), + FirstName = table.Column(nullable: true), + LastName = table.Column(nullable: true), + MiddleName = table.Column(nullable: true), + PasswordHash = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + UserName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Addresses_AddressId", + column: x => x.AddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "Orders", columns: table => new @@ -170,18 +177,32 @@ protected override void Up(MigrationBuilder migrationBuilder) CreatedUtc = table.Column(type: "DateTime2", nullable: false), ModifiedById = table.Column(nullable: true), ModifiedUtc = table.Column(type: "DateTime2", nullable: false), + BillingAddressId = table.Column(nullable: true), OrderStatusId = table.Column(nullable: false), + ShippingAddressId = table.Column(nullable: true), UserId = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Addresses_BillingAddressId", + column: x => x.BillingAddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_Orders_OrderStatus_OrderStatusId", column: x => x.OrderStatusId, principalTable: "OrderStatus", principalColumn: "Id", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Orders_Addresses_ShippingAddressId", + column: x => x.ShippingAddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_Orders_Users_UserId", column: x => x.UserId, @@ -201,9 +222,9 @@ protected override void Up(MigrationBuilder migrationBuilder) ModifiedById = table.Column(nullable: true), ModifiedUtc = table.Column(type: "DateTime2", nullable: false), OrderId = table.Column(nullable: false), + Price = table.Column(type: "Decimal(10, 2)", nullable: false), ProductId = table.Column(nullable: false), - Quantity = table.Column(nullable: false), - Price = table.Column(type: "Decimal(10, 2)", nullable: false) + Quantity = table.Column(nullable: false) }, constraints: table => { @@ -313,17 +334,21 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.InsertData( table: "Users", - columns: new[] { "Id", "CreatedById", "CreatedUtc", "DeletedById", "DeletedUtc", "FirstName", "LastName", "MiddleName", "ModifiedById", "ModifiedUtc", "PasswordHash", "PhoneNumber", "UserName" }, + columns: new[] { "Id", "AddressId", "CreatedById", "CreatedUtc", "DeletedById", "DeletedUtc", "FirstName", "LastName", "MiddleName", "ModifiedById", "ModifiedUtc", "PasswordHash", "PhoneNumber", "UserName" }, values: new object[,] { - { 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "System", "Admin", null, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "", "987-654-3210", "admin" }, - { 2, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "John", "Customer", "Q", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "", "987-654-3210", "customer" } + { 1, null, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "System", "Admin", null, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "", "987-654-3210", "admin" }, + { 2, null, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "John", "Customer", "Q", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "", "987-654-3210", "customer" } }); migrationBuilder.InsertData( table: "Addresses", columns: new[] { "Id", "City", "CreatedById", "CreatedUtc", "DeletedById", "DeletedUtc", "Line1", "Line2", "ModifiedById", "ModifiedUtc", "StateId", "ZipCode" }, - values: new object[] { 1, "Anytown", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "123 Any St.", "Suite 456", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, "12345" }); + values: new object[,] + { + { 1, "Anytown", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "Billing Dept.", "123 Any St.", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, "12345" }, + { 2, "Anytown", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "Receiving Dept.", "123 Any St.", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, "12345" } + }); migrationBuilder.InsertData( table: "Categories", @@ -336,23 +361,38 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.InsertData( table: "Orders", - columns: new[] { "Id", "CreatedById", "CreatedUtc", "ModifiedById", "ModifiedUtc", "OrderStatusId", "UserId" }, - values: new object[] { 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 4, 1 }); - - migrationBuilder.InsertData( - table: "Products", - columns: new[] { "Id", "CategoryId", "CreatedById", "CreatedUtc", "DeletedById", "DeletedUtc", "Description", "ModifiedById", "ModifiedUtc", "Name", "Price", "ProductStatusId" }, - values: new object[] { 1, 2, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "A men's t-shirt", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "T-Shirt", 1.00m, 1 }); + columns: new[] { "Id", "BillingAddressId", "CreatedById", "CreatedUtc", "ModifiedById", "ModifiedUtc", "OrderStatusId", "ShippingAddressId", "UserId" }, + values: new object[,] + { + { 1, 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 4, 2, 1 }, + { 2, 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 3, 2, 1 }, + { 3, 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 2, 2, 1 }, + { 4, 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, 2, 1 } + }); migrationBuilder.InsertData( table: "Products", columns: new[] { "Id", "CategoryId", "CreatedById", "CreatedUtc", "DeletedById", "DeletedUtc", "Description", "ModifiedById", "ModifiedUtc", "Name", "Price", "ProductStatusId" }, - values: new object[] { 2, 3, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "A wommen's t-shirt", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "T-Shirt", 1.00m, 1 }); + values: new object[,] + { + { 1, 2, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "A men's t-shirt", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "T-Shirt", 1.00m, 1 }, + { 2, 3, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), null, null, "A wommen's t-shirt", 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), "T-Shirt", 1.00m, 1 } + }); migrationBuilder.InsertData( table: "OrderItems", columns: new[] { "Id", "CreatedById", "CreatedUtc", "ModifiedById", "ModifiedUtc", "OrderId", "Price", "ProductId", "Quantity" }, - values: new object[] { 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, 1.00m, 1, 1 }); + values: new object[,] + { + { 1, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, 1.00m, 1, 1 }, + { 2, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, 1.00m, 2, 1 }, + { 3, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 2, 1.00m, 1, 1 }, + { 4, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 2, 1.00m, 2, 1 }, + { 5, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 3, 1.00m, 1, 1 }, + { 6, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 3, 1.00m, 2, 1 }, + { 7, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 4, 1.00m, 1, 1 }, + { 8, 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 1, new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), 4, 1.00m, 2, 1 } + }); migrationBuilder.CreateIndex( name: "IX_Addresses_StateId", @@ -374,11 +414,21 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "OrderItems", column: "ProductId"); + migrationBuilder.CreateIndex( + name: "IX_Orders_BillingAddressId", + table: "Orders", + column: "BillingAddressId"); + migrationBuilder.CreateIndex( name: "IX_Orders_OrderStatusId", table: "Orders", column: "OrderStatusId"); + migrationBuilder.CreateIndex( + name: "IX_Orders_ShippingAddressId", + table: "Orders", + column: "ShippingAddressId"); + migrationBuilder.CreateIndex( name: "IX_Orders_UserId", table: "Orders", @@ -393,19 +443,18 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "IX_Products_ProductStatusId", table: "Products", column: "ProductStatusId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_AddressId", + table: "Users", + column: "AddressId"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "Addresses"); - migrationBuilder.DropTable( name: "OrderItems"); - migrationBuilder.DropTable( - name: "States"); - migrationBuilder.DropTable( name: "Orders"); @@ -423,6 +472,12 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "ProductStatuses"); + + migrationBuilder.DropTable( + name: "Addresses"); + + migrationBuilder.DropTable( + name: "States"); } } } diff --git a/Store.Domain/Migrations/StoreContextModelSnapshot.cs b/Store.Domain/Migrations/StoreContextModelSnapshot.cs index 505b5a9..041db7d 100644 --- a/Store.Domain/Migrations/StoreContextModelSnapshot.cs +++ b/Store.Domain/Migrations/StoreContextModelSnapshot.cs @@ -62,8 +62,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) City = "Anytown", CreatedById = 1, CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - Line1 = "123 Any St.", - Line2 = "Suite 456", + Line1 = "Billing Dept.", + Line2 = "123 Any St.", + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + StateId = 1, + ZipCode = "12345" + }, + new + { + Id = 2, + City = "Anytown", + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Line1 = "Receiving Dept.", + Line2 = "123 Any St.", ModifiedById = 1, ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), StateId = 1, @@ -118,6 +131,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + b.Property("BillingAddressId"); + b.Property("CreatedById"); b.Property("CreatedUtc") @@ -130,12 +145,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrderStatusId"); + b.Property("ShippingAddressId"); + b.Property("UserId"); b.HasKey("Id"); + b.HasIndex("BillingAddressId"); + b.HasIndex("OrderStatusId"); + b.HasIndex("ShippingAddressId"); + b.HasIndex("UserId"); b.ToTable("Orders"); @@ -144,11 +165,49 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 1, + BillingAddressId = 1, CreatedById = 1, CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), ModifiedById = 1, ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), OrderStatusId = 4, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 2, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 3, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 3, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 2, + ShippingAddressId = 2, + UserId = 1 + }, + new + { + Id = 4, + BillingAddressId = 1, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderStatusId = 1, + ShippingAddressId = 2, UserId = 1 }); }); @@ -198,6 +257,90 @@ protected override void BuildModel(ModelBuilder modelBuilder) Price = 1.00m, ProductId = 1, Quantity = 1 + }, + new + { + Id = 2, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 1, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 3, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 2, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 4, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 2, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 5, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 3, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 6, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 3, + Price = 1.00m, + ProductId = 2, + Quantity = 1 + }, + new + { + Id = 7, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 4, + Price = 1.00m, + ProductId = 1, + Quantity = 1 + }, + new + { + Id = 8, + CreatedById = 1, + CreatedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + ModifiedById = 1, + ModifiedUtc = new DateTime(2019, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + OrderId = 4, + Price = 1.00m, + ProductId = 2, + Quantity = 1 }); }); @@ -715,6 +858,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + b.Property("AddressId"); + b.Property("CreatedById"); b.Property("CreatedUtc") @@ -743,6 +888,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("AddressId"); + b.ToTable("Users"); b.HasData( @@ -792,11 +939,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Store.Domain.Models.Order", b => { + b.HasOne("Store.Domain.Models.Address", "BillingAddress") + .WithMany() + .HasForeignKey("BillingAddressId"); + b.HasOne("Store.Domain.Models.OrderStatus", "OrderStatus") .WithMany() .HasForeignKey("OrderStatusId") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Store.Domain.Models.Address", "ShippingAddress") + .WithMany() + .HasForeignKey("ShippingAddressId"); + b.HasOne("Store.Domain.Models.User", "User") .WithMany() .HasForeignKey("UserId") @@ -828,6 +983,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("ProductStatusId") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("Store.Domain.Models.User", b => + { + b.HasOne("Store.Domain.Models.Address", "Address") + .WithMany() + .HasForeignKey("AddressId"); + }); #pragma warning restore 612, 618 } } diff --git a/Store.Domain/Models/Order.cs b/Store.Domain/Models/Order.cs index fca8055..cb429a0 100644 --- a/Store.Domain/Models/Order.cs +++ b/Store.Domain/Models/Order.cs @@ -11,12 +11,22 @@ public Order() OrderItems = new HashSet(); } + public Address BillingAddress { get; set; } + + [ForeignKey(nameof(BillingAddress))] + public int? BillingAddressId { get; set; } + public ICollection OrderItems { get; set; } public OrderStatus OrderStatus { get; set; } [ForeignKey(nameof(OrderStatus))] public int OrderStatusId { get; set; } + public Address ShippingAddress { get; set; } + + [ForeignKey(nameof(ShippingAddress))] + public int? ShippingAddressId { get; set; } + public User User { get; set; } [ForeignKey(nameof(User))] diff --git a/Store.Domain/Models/OrderItem.cs b/Store.Domain/Models/OrderItem.cs index b4bf7f3..23b05cd 100644 --- a/Store.Domain/Models/OrderItem.cs +++ b/Store.Domain/Models/OrderItem.cs @@ -10,12 +10,13 @@ public class OrderItem : Entity [ForeignKey(nameof(Order))] public int OrderId { get; set; } + public decimal Price { get; set; } + public Product Product { get; set; } [ForeignKey(nameof(Product))] public int ProductId { get; set; } public int Quantity { get; set; } - public decimal Price { get; set; } } } diff --git a/Store.Domain/Models/OrderStatus.cs b/Store.Domain/Models/OrderStatus.cs index 3311db0..0e9e211 100644 --- a/Store.Domain/Models/OrderStatus.cs +++ b/Store.Domain/Models/OrderStatus.cs @@ -10,7 +10,7 @@ public enum Ids Received = 1, Processing = 2, Shipping = 3, - Shipped = 4, + Shipped = 4 } } } diff --git a/Store.Domain/Models/StoreContext.Seed.cs b/Store.Domain/Models/StoreContext.Seed.cs index c0896d6..450b52a 100644 --- a/Store.Domain/Models/StoreContext.Seed.cs +++ b/Store.Domain/Models/StoreContext.Seed.cs @@ -27,7 +27,8 @@ private void SeedAddressTable(ModelBuilder modelBuilder) { var data = new[] { - new Address { Id = 1, Line1 = "123 Any St.", Line2 = "Suite 456", City = "Anytown", StateId = 1, ZipCode = "12345", CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } + new Address { Id = 1, Line1 = "Billing Dept.", Line2 = "123 Any St.", City = "Anytown", StateId = 1, ZipCode = "12345", CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new Address { Id = 2, Line1 = "Receiving Dept.", Line2 = "123 Any St.", City = "Anytown", StateId = 1, ZipCode = "12345", CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } }; modelBuilder.Entity
(entity => entity.HasData(data)); @@ -49,7 +50,17 @@ private void SeedOrderItemTable(ModelBuilder modelBuilder) { var data = new[] { - new OrderItem { Id = 1, OrderId = 1, ProductId = 1, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } + new OrderItem { Id = 1, OrderId = 1, ProductId = 1, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new OrderItem { Id = 2, OrderId = 1, ProductId = 2, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + + new OrderItem { Id = 3, OrderId = 2, ProductId = 1, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new OrderItem { Id = 4, OrderId = 2, ProductId = 2, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + + new OrderItem { Id = 5, OrderId = 3, ProductId = 1, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new OrderItem { Id = 6, OrderId = 3, ProductId = 2, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + + new OrderItem { Id = 7, OrderId = 4, ProductId = 1, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new OrderItem { Id = 8, OrderId = 4, ProductId = 2, Quantity = 1, Price = 1.00m, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } }; modelBuilder.Entity(entity => entity.HasData(data)); @@ -72,7 +83,10 @@ private void SeedOrderTable(ModelBuilder modelBuilder) { var data = new[] { - new Order { Id = 1, OrderStatusId = (int)OrderStatus.Ids.Shipped, UserId = AdminUserId, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } + new Order { Id = 1, OrderStatusId = (int)OrderStatus.Ids.Shipped, UserId = AdminUserId, BillingAddressId = 1, ShippingAddressId = 2, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new Order { Id = 2, OrderStatusId = (int)OrderStatus.Ids.Shipping, UserId = AdminUserId, BillingAddressId = 1, ShippingAddressId = 2, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new Order { Id = 3, OrderStatusId = (int)OrderStatus.Ids.Processing, UserId = AdminUserId, BillingAddressId = 1, ShippingAddressId = 2, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime }, + new Order { Id = 4, OrderStatusId = (int)OrderStatus.Ids.Received, UserId = AdminUserId, BillingAddressId = 1, ShippingAddressId = 2, CreatedById = AdminUserId, CreatedUtc = _seedDateTime, ModifiedById = AdminUserId, ModifiedUtc = _seedDateTime } }; modelBuilder.Entity(entity => entity.HasData(data)); @@ -166,7 +180,6 @@ private void SeedStateTable(ModelBuilder modelBuilder) modelBuilder.Entity(entity => entity.HasData(data)); } - private void SeedUserTable(ModelBuilder modelBuilder) { var data = new[] diff --git a/Store.Domain/Models/StoreContext.cs b/Store.Domain/Models/StoreContext.cs index 1aa26e3..d0ad7f8 100644 --- a/Store.Domain/Models/StoreContext.cs +++ b/Store.Domain/Models/StoreContext.cs @@ -42,10 +42,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .HasOne(x => x.BillingAddress) + .WithMany() + .OnDelete(DeleteBehavior.ClientSetNull); + + modelBuilder.Entity() + .HasOne(x => x.ShippingAddress) + .WithMany() + .OnDelete(DeleteBehavior.ClientSetNull); + modelBuilder.Entity(entity => { entity.Property(x => x.Price).HasColumnType("Decimal(10, 2)"); }); modelBuilder.Entity(entity => { entity.Property(x => x.Price).HasColumnType("Decimal(10, 2)"); }); + modelBuilder.Entity() + .HasOne(x => x.Address) + .WithMany() + .OnDelete(DeleteBehavior.ClientSetNull); + Seed(modelBuilder); } diff --git a/Store.Domain/Models/User.cs b/Store.Domain/Models/User.cs index c1a1f83..2a4a514 100644 --- a/Store.Domain/Models/User.cs +++ b/Store.Domain/Models/User.cs @@ -1,9 +1,15 @@ -using Store.Domain.Framework; +using System.ComponentModel.DataAnnotations.Schema; +using Store.Domain.Framework; namespace Store.Domain.Models { public class User : SoftDeleteableEntity { + public Address Address { get; set; } + + [ForeignKey(nameof(Address))] + public int? AddressId { get; set; } + public string FirstName { get; set; } public string LastName { get; set; } public string MiddleName { get; set; } diff --git a/Store.Domain/Repositories/AddressRepository.cs b/Store.Domain/Repositories/AddressRepository.cs index 6943a60..cea2442 100644 --- a/Store.Domain/Repositories/AddressRepository.cs +++ b/Store.Domain/Repositories/AddressRepository.cs @@ -9,7 +9,6 @@ namespace Store.Domain.Repositories { public interface IAddressRepository : IRepository
{ - } public class AddressRepository : Repository
, IAddressRepository diff --git a/Store.Domain/Repositories/OrderRepository.cs b/Store.Domain/Repositories/OrderRepository.cs index 6ba2f60..a0f9b8a 100644 --- a/Store.Domain/Repositories/OrderRepository.cs +++ b/Store.Domain/Repositories/OrderRepository.cs @@ -21,6 +21,8 @@ protected override IQueryable GetQuery(int currentUserId, Expression x.OrderItems) + .ThenInclude(x => x.Product) + .ThenInclude(x => x.Category) .Include(x => x.OrderStatus); return query; diff --git a/Store.Domain/Repositories/ProductRepository.cs b/Store.Domain/Repositories/ProductRepository.cs index bb6bc10..9faf512 100644 --- a/Store.Domain/Repositories/ProductRepository.cs +++ b/Store.Domain/Repositories/ProductRepository.cs @@ -21,6 +21,7 @@ protected override IQueryable GetQuery(int currentUserId, Expression> predicate = null) { var query = GetBaseQuery(currentUserId, predicate) + .Include(x => x.Category) .Include(x => x.ProductStatus); return query; diff --git a/Store.Domain/Store.Domain.csproj b/Store.Domain/Store.Domain.csproj index a0af25e..8d09c74 100644 --- a/Store.Domain/Store.Domain.csproj +++ b/Store.Domain/Store.Domain.csproj @@ -33,4 +33,8 @@ + + + + diff --git a/Store.Services/.Framework/LookupDto.cs b/Store.Services/.Framework/LookupDto.cs new file mode 100644 index 0000000..43f93f7 --- /dev/null +++ b/Store.Services/.Framework/LookupDto.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Store.Services.Framework +{ + public abstract class LookupDto : Dto + { + [StringLength(255)] + public string Description { get; set; } + + [Required] + [StringLength(255)] + public string Name { get; set; } + } +} diff --git a/Store.Services/Contracts/Category/CategoryDto.cs b/Store.Services/Contracts/Category/CategoryDto.cs index eabd1e8..1116e94 100644 --- a/Store.Services/Contracts/Category/CategoryDto.cs +++ b/Store.Services/Contracts/Category/CategoryDto.cs @@ -2,7 +2,7 @@ namespace Store.Services.Contracts.Category { - public class CategoryDto : Dto + public class CategoryDto : LookupDto { } } diff --git a/Store.Services/Contracts/Order/OrderDto.cs b/Store.Services/Contracts/Order/OrderDto.cs index 23e2d19..474caca 100644 --- a/Store.Services/Contracts/Order/OrderDto.cs +++ b/Store.Services/Contracts/Order/OrderDto.cs @@ -1,8 +1,18 @@ -using Store.Services.Framework; +using System.Collections.Generic; +using Store.Services.Contracts.User; +using Store.Services.Framework; namespace Store.Services.Contracts.Order { public class OrderDto : Dto { + public OrderDto() + { + OrderItems = new HashSet(); + } + + public ICollection OrderItems { get; set; } + public OrderStatusDto OrderStatus { get; set; } + public UserDto User { get; set; } } } diff --git a/Store.Services/Contracts/Order/OrderItemDto.cs b/Store.Services/Contracts/Order/OrderItemDto.cs index 0e9ace2..1dd5c25 100644 --- a/Store.Services/Contracts/Order/OrderItemDto.cs +++ b/Store.Services/Contracts/Order/OrderItemDto.cs @@ -1,8 +1,12 @@ -using Store.Services.Framework; +using Store.Services.Contracts.Product; +using Store.Services.Framework; namespace Store.Services.Contracts.Order { public class OrderItemDto : Dto { + public decimal Price { get; set; } + public ProductDto Product { get; set; } + public int Quantity { get; set; } } } diff --git a/Store.Services/Contracts/Order/OrderStatusDto.cs b/Store.Services/Contracts/Order/OrderStatusDto.cs new file mode 100644 index 0000000..2eda997 --- /dev/null +++ b/Store.Services/Contracts/Order/OrderStatusDto.cs @@ -0,0 +1,8 @@ +using Store.Services.Framework; + +namespace Store.Services.Contracts.Order +{ + public class OrderStatusDto : LookupDto + { + } +} diff --git a/Store.Services/Contracts/Product/ProductDto.cs b/Store.Services/Contracts/Product/ProductDto.cs new file mode 100644 index 0000000..8c3c08c --- /dev/null +++ b/Store.Services/Contracts/Product/ProductDto.cs @@ -0,0 +1,14 @@ +using Store.Services.Contracts.Category; +using Store.Services.Framework; + +namespace Store.Services.Contracts.Product +{ + public class ProductDto : Dto + { + public CategoryDto Category { get; set; } + public string Description { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } + public ProductStatusDto ProductStatus { get; set; } + } +} diff --git a/Store.Services/Contracts/Product/ProductStatusDto.cs b/Store.Services/Contracts/Product/ProductStatusDto.cs new file mode 100644 index 0000000..ed8beaf --- /dev/null +++ b/Store.Services/Contracts/Product/ProductStatusDto.cs @@ -0,0 +1,8 @@ +using Store.Services.Framework; + +namespace Store.Services.Contracts.Product +{ + public class ProductStatusDto : LookupDto + { + } +} diff --git a/Store.Services/Contracts/User/UserDto.cs b/Store.Services/Contracts/User/UserDto.cs new file mode 100644 index 0000000..4419be7 --- /dev/null +++ b/Store.Services/Contracts/User/UserDto.cs @@ -0,0 +1,8 @@ +using Store.Services.Framework; + +namespace Store.Services.Contracts.User +{ + public class UserDto : Dto + { + } +} diff --git a/Store.Services/Mapping/CategoryMapper.cs b/Store.Services/Mapping/CategoryMapper.cs new file mode 100644 index 0000000..c4a7f75 --- /dev/null +++ b/Store.Services/Mapping/CategoryMapper.cs @@ -0,0 +1,9 @@ +using Store.Domain.Models; +using Store.Services.Contracts.Category; + +namespace Store.Services.Mapping +{ + public class CategoryMapper : Mapper + { + } +} diff --git a/Store.Services/Mapping/Mapper.cs b/Store.Services/Mapping/Mapper.cs new file mode 100644 index 0000000..262cb39 --- /dev/null +++ b/Store.Services/Mapping/Mapper.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using AutoMapper; + +namespace Store.Services.Mapping +{ + /// Base class for all object-to-object mapper classes. + /// + public abstract class Mapper : Profile + { + } + + /// Base class that handles the most common mappings from a given source to a destination. + /// The type of the source object. + /// The type of the destination object. + /// + public abstract class Mapper : Mapper + { + protected Mapper() + { + CreateMap(); + } + + protected IMappingExpression CreateMap() + { + return CreateMap(); + } + + public static TDest Map(TSource source) + { + return AutoMapper.Mapper.Map(source); + } + + public static TDest Map(TSource source, TDest dest) + { + AutoMapper.Mapper.Map(source, dest); + + return dest; + } + + public static List Map(IEnumerable source) + { + return AutoMapper.Mapper.Map, List>(source); + } + } +} diff --git a/Store.Services/Mapping/OrderMapper.cs b/Store.Services/Mapping/OrderMapper.cs new file mode 100644 index 0000000..4fa85e5 --- /dev/null +++ b/Store.Services/Mapping/OrderMapper.cs @@ -0,0 +1,9 @@ +using Store.Domain.Models; +using Store.Services.Contracts.Order; + +namespace Store.Services.Mapping +{ + public class OrderMapper : Mapper + { + } +} diff --git a/Store.Services/Mapping/ProductDtoMapper.cs b/Store.Services/Mapping/ProductDtoMapper.cs new file mode 100644 index 0000000..891b1c9 --- /dev/null +++ b/Store.Services/Mapping/ProductDtoMapper.cs @@ -0,0 +1,9 @@ +using Store.Domain.Models; +using Store.Services.Contracts.Product; + +namespace Store.Services.Mapping +{ + public class ProductDtoMapper : Mapper + { + } +} diff --git a/Store.Services/Mapping/ProductMapper.cs b/Store.Services/Mapping/ProductMapper.cs new file mode 100644 index 0000000..70e4d0a --- /dev/null +++ b/Store.Services/Mapping/ProductMapper.cs @@ -0,0 +1,9 @@ +using Store.Domain.Models; +using Store.Services.Contracts.Product; + +namespace Store.Services.Mapping +{ + public class ProductMapper : Mapper + { + } +} diff --git a/Store.Services/Services/AddressService.cs b/Store.Services/Services/AddressService.cs index c7768c2..187fe47 100644 --- a/Store.Services/Services/AddressService.cs +++ b/Store.Services/Services/AddressService.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using Store.Common.Contracts; using Store.Domain.Repositories; using Store.Services.Contracts.Address; using Store.Services.Framework; @@ -9,11 +11,9 @@ namespace Store.Services public interface IAddressService : IService { Task AddAsync(int userId, AddressDto dto); - Task DeleteAsync(int userId, int id); - Task GetAsync(int userId, int id); - + Task> GetAsync(int userId, PagingOptions pagingOptions); Task UpdateAsync(int userId, AddressDto dto); } @@ -31,11 +31,8 @@ public async Task AddAsync(int userId, AddressDto dto) var model = AddressMapper.Map(dto); await _addressRepository.AddAsync(userId, model); - // Retrieve a fresh copy of the saved object to return - model = await _addressRepository.GetAsync(userId, model.Id); - var result = AddressDtoMapper.Map(model); - - return result; + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); } public async Task DeleteAsync(int userId, int id) @@ -54,6 +51,14 @@ public async Task GetAsync(int userId, int id) return result; } + public async Task> GetAsync(int userId, PagingOptions pagingOptions) + { + var models = await _addressRepository.GetAsync(userId, pagingOptions); + var results = AddressDtoMapper.Map(models); + + return results; + } + public async Task UpdateAsync(int userId, AddressDto dto) { var model = await _addressRepository.GetAsync(userId, dto.Id); @@ -65,10 +70,8 @@ public async Task UpdateAsync(int userId, AddressDto dto) AddressMapper.Map(dto, model); await _addressRepository.SaveChangesAsync(userId); - model = await _addressRepository.GetAsync(userId, model.Id); - var result = AddressDtoMapper.Map(model); - - return result; + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); } } } diff --git a/Store.Services/Services/CategoryService.cs b/Store.Services/Services/CategoryService.cs index c871014..d05058a 100644 --- a/Store.Services/Services/CategoryService.cs +++ b/Store.Services/Services/CategoryService.cs @@ -1,8 +1,77 @@ -using Store.Services.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using Store.Common.Contracts; +using Store.Domain.Repositories; +using Store.Services.Contracts.Category; +using Store.Services.Framework; +using Store.Services.Mapping; namespace Store.Services { - public class CategoryService : Service + public interface ICategoryService : IService { + Task AddAsync(int userId, CategoryDto dto); + Task DeleteAsync(int userId, int id); + Task GetAsync(int userId, int id); + Task> GetAsync(int userId, PagingOptions pagingOptions); + Task UpdateAsync(int userId, CategoryDto dto); + } + + public class CategoryService : Service, ICategoryService + { + private readonly ICategoryRepository _categoryRepository; + + public CategoryService(ICategoryRepository categoryRepository) + { + _categoryRepository = categoryRepository; + } + + public async Task AddAsync(int userId, CategoryDto dto) + { + var model = CategoryMapper.Map(dto); + await _categoryRepository.AddAsync(userId, model); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } + + public async Task DeleteAsync(int userId, int id) + { + var model = await _categoryRepository.DeleteAsync(userId, id); + var result = CategoryDtoMapper.Map(model); + + return result; + } + + public async Task GetAsync(int userId, int id) + { + var model = await _categoryRepository.GetAsync(userId, id); + var result = CategoryDtoMapper.Map(model); + + return result; + } + + public async Task> GetAsync(int userId, PagingOptions pagingOptions) + { + var models = await _categoryRepository.GetAsync(userId, pagingOptions); + var results = CategoryDtoMapper.Map(models); + + return results; + } + + public async Task UpdateAsync(int userId, CategoryDto dto) + { + var model = await _categoryRepository.GetAsync(userId, dto.Id); + if (model == null) + { + return null; + } + + CategoryMapper.Map(dto, model); + await _categoryRepository.SaveChangesAsync(userId); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } } } diff --git a/Store.Services/Services/OrderService.cs b/Store.Services/Services/OrderService.cs index 4b148b6..5ce2439 100644 --- a/Store.Services/Services/OrderService.cs +++ b/Store.Services/Services/OrderService.cs @@ -1,8 +1,77 @@ -using Store.Services.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using Store.Common.Contracts; +using Store.Domain.Repositories; +using Store.Services.Contracts.Order; +using Store.Services.Framework; +using Store.Services.Mapping; namespace Store.Services { - public class OrderService : Service + public interface IOrderService : IService { + Task AddAsync(int userId, OrderDto dto); + Task DeleteAsync(int userId, int id); + Task GetAsync(int userId, int id); + Task> GetAsync(int userId, PagingOptions pagingOptions); + Task UpdateAsync(int userId, OrderDto dto); + } + + public class OrderService : Service, IOrderService + { + private readonly IOrderRepository _orderRepository; + + public OrderService(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } + + public async Task AddAsync(int userId, OrderDto dto) + { + var model = OrderMapper.Map(dto); + await _orderRepository.AddAsync(userId, model); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } + + public async Task DeleteAsync(int userId, int id) + { + var model = await _orderRepository.DeleteAsync(userId, id); + var result = OrderDtoMapper.Map(model); + + return result; + } + + public async Task GetAsync(int userId, int id) + { + var model = await _orderRepository.GetAsync(userId, id); + var result = OrderDtoMapper.Map(model); + + return result; + } + + public async Task> GetAsync(int userId, PagingOptions pagingOptions) + { + var models = await _orderRepository.GetAsync(userId, pagingOptions); + var results = OrderDtoMapper.Map(models); + + return results; + } + + public async Task UpdateAsync(int userId, OrderDto dto) + { + var model = await _orderRepository.GetAsync(userId, dto.Id); + if (model == null) + { + return null; + } + + OrderMapper.Map(dto, model); + await _orderRepository.SaveChangesAsync(userId); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } } } diff --git a/Store.Services/Services/ProductService.cs b/Store.Services/Services/ProductService.cs index 0fb5d5e..c79196e 100644 --- a/Store.Services/Services/ProductService.cs +++ b/Store.Services/Services/ProductService.cs @@ -1,8 +1,77 @@ -using Store.Services.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using Store.Common.Contracts; +using Store.Domain.Repositories; +using Store.Services.Contracts.Product; +using Store.Services.Framework; +using Store.Services.Mapping; namespace Store.Services { - public class ProductService : Service + public interface IProductService : IService { + Task AddAsync(int userId, ProductDto dto); + Task DeleteAsync(int userId, int id); + Task GetAsync(int userId, int id); + Task> GetAsync(int userId, PagingOptions pagingOptions); + Task UpdateAsync(int userId, ProductDto dto); + } + + public class ProductService : Service, IProductService + { + private readonly IProductRepository _productRepository; + + public ProductService(IProductRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task AddAsync(int userId, ProductDto dto) + { + var model = ProductMapper.Map(dto); + await _productRepository.AddAsync(userId, model); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } + + public async Task DeleteAsync(int userId, int id) + { + var model = await _productRepository.DeleteAsync(userId, id); + var result = ProductDtoMapper.Map(model); + + return result; + } + + public async Task GetAsync(int userId, int id) + { + var model = await _productRepository.GetAsync(userId, id); + var result = ProductDtoMapper.Map(model); + + return result; + } + + public async Task> GetAsync(int userId, PagingOptions pagingOptions) + { + var models = await _productRepository.GetAsync(userId, pagingOptions); + var results = ProductDtoMapper.Map(models); + + return results; + } + + public async Task UpdateAsync(int userId, ProductDto dto) + { + var model = await _productRepository.GetAsync(userId, dto.Id); + if (model == null) + { + return null; + } + + ProductMapper.Map(dto, model); + await _productRepository.SaveChangesAsync(userId); + + // Return a fresh copy of the saved object. + return await GetAsync(userId, model.Id); + } } } diff --git a/Store.Services/Store.Services.csproj b/Store.Services/Store.Services.csproj index fa114f1..17993ad 100644 --- a/Store.Services/Store.Services.csproj +++ b/Store.Services/Store.Services.csproj @@ -18,13 +18,14 @@ + - + diff --git a/Store.Services/Store.Services.xml b/Store.Services/Store.Services.xml index 9d6c916..c5febae 100644 --- a/Store.Services/Store.Services.xml +++ b/Store.Services/Store.Services.xml @@ -37,7 +37,7 @@ Base class for all object-to-object mapper classes. - + Base class that handles the most common mappings from a given source to a destination. diff --git a/Store.Tests.Integration/Store.Tests.Integration.csproj b/Store.Tests.Integration/Store.Tests.Integration.csproj index b33d523..4f79c47 100644 --- a/Store.Tests.Integration/Store.Tests.Integration.csproj +++ b/Store.Tests.Integration/Store.Tests.Integration.csproj @@ -15,7 +15,9 @@ + + diff --git a/Store.Web/.Framework/StoreMvcController.cs b/Store.Web/.Framework/StoreMvcController.cs index a975f53..aae09d2 100644 --- a/Store.Web/.Framework/StoreMvcController.cs +++ b/Store.Web/.Framework/StoreMvcController.cs @@ -4,5 +4,7 @@ namespace Store.Web.Framework { public abstract class StoreMvcController : Controller { + // Fake user Id for now, there are so many ways to do this, and it's out of scope for this demo + protected int UserId => 1; } } diff --git a/Store.Web/Controllers/Api/AddressController.cs b/Store.Web/Controllers/Api/AddressController.cs index 07bab72..bcc4794 100644 --- a/Store.Web/Controllers/Api/AddressController.cs +++ b/Store.Web/Controllers/Api/AddressController.cs @@ -2,6 +2,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Store.Common.Contracts; using Store.Common.Validation; using Store.Services; using Store.Services.Contracts.Address; @@ -39,14 +40,27 @@ public async Task DeleteAsync(int id) return NoContent(); } + /// Get all Addresses. + [HttpGet("", Name = "Addresses_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task>> GetAsync([FromQuery] PagingOptions pagingOptions) + { + var result = await _addressService.GetAsync(UserId, pagingOptions); + + return Ok(result); + } + /// Get a an Address by Id. [HttpGet("{id}", Name = "Address_Get")] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] - public async Task>> GetAsync(int id) + public async Task> GetAsync(int id) { var result = await _addressService.GetAsync(UserId, id); - return Ok(result); + if (result == null) + return NotFound(); + else + return Ok(result); } diff --git a/Store.Web/Controllers/Api/CategoryController.cs b/Store.Web/Controllers/Api/CategoryController.cs new file mode 100644 index 0000000..e75215e --- /dev/null +++ b/Store.Web/Controllers/Api/CategoryController.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Store.Common.Contracts; +using Store.Common.Validation; +using Store.Services; +using Store.Services.Contracts.Category; +using Store.Web.Framework; + +namespace Store.Web.Controllers.Api +{ + /// Functionality relating to store management. + /// + [ApiController] + [ApiVersion("1")] + [Route("api/v{version:apiVersion}/category")] + public class CategoryController : StoreApiController + { + private readonly ICategoryService _categoryService; + + public CategoryController(ICategoryService categoryService) + { + _categoryService = categoryService; + } + + /// Delete the specified Category. + /// The Id of the Category to be deleted. + [HttpDelete("{id}", Name = "Category_Delete")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public async Task DeleteAsync(int id) + { + var result = await _categoryService.DeleteAsync(UserId, id); + + if (result == null) + { + return NotFound(); + } + + return NoContent(); + } + + /// Get all Categories. + [HttpGet("", Name = "Categories_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task>> GetAsync([FromQuery] PagingOptions pagingOptions) + { + var result = await _categoryService.GetAsync(UserId, pagingOptions); + + return Ok(result); + } + + /// Get a Category by Id. + [HttpGet("{id}", Name = "Category_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task> GetAsync(int id) + { + var result = await _categoryService.GetAsync(UserId, id); + + if (result == null) + return NotFound(); + else + return Ok(result); + } + + + /// Creates a Category. + /// The Category to be created. + [HttpPost("{id}", Name = "Category_Post")] + [ProducesResponseType((int)HttpStatusCode.Created)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PostAsync(CategoryDto body) + { + var result = await _categoryService.AddAsync(UserId, body); + + if (result == null) + { + return BadRequest(); + } + + return Created($"{HttpContext.Request.Path}/{result.Id}", result); + } + + /// Updates a Category. + /// + [HttpPut("{id}", Name = "Category_Put")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PutAsync(int id, CategoryDto body) + { + ValidationHelper.Validate(body.Id == id, nameof(body.Id), ValidationHelper.KeyDoesNotMatchRoute); + + var result = await _categoryService.UpdateAsync(UserId, body); + + return Ok(result); + } + } +} diff --git a/Store.Web/Controllers/Api/OrderController.cs b/Store.Web/Controllers/Api/OrderController.cs new file mode 100644 index 0000000..83b1fd2 --- /dev/null +++ b/Store.Web/Controllers/Api/OrderController.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Store.Common.Contracts; +using Store.Common.Validation; +using Store.Services; +using Store.Services.Contracts.Order; +using Store.Web.Framework; + +namespace Store.Web.Controllers.Api +{ + /// Functionality relating to store management. + /// + [ApiController] + [ApiVersion("1")] + [Route("api/v{version:apiVersion}/order")] + public class OrderController : StoreApiController + { + private readonly IOrderService _orderService; + + public OrderController(IOrderService orderService) + { + _orderService = orderService; + } + + /// Delete the specified Order. + /// The Id of the Order to be deleted. + [HttpDelete("{id}", Name = "Order_Delete")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public async Task DeleteAsync(int id) + { + var result = await _orderService.DeleteAsync(UserId, id); + + if (result == null) + { + return NotFound(); + } + + return NoContent(); + } + + /// Get all Orders. + [HttpGet("", Name = "Orders_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task>> GetAsync([FromQuery] PagingOptions pagingOptions) + { + var result = await _orderService.GetAsync(UserId, pagingOptions); + + return Ok(result); + } + + /// Get a an Order by Id. + [HttpGet("{id}", Name = "Order_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task> GetAsync(int id) + { + var result = await _orderService.GetAsync(UserId, id); + + if (result == null) + return NotFound(); + else + return Ok(result); + } + + + /// Creates an Order. + /// The Order to be created. + [HttpPost("{id}", Name = "Order_Post")] + [ProducesResponseType((int)HttpStatusCode.Created)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PostAsync(OrderDto body) + { + var result = await _orderService.AddAsync(UserId, body); + + if (result == null) + { + return BadRequest(); + } + + return Created($"{HttpContext.Request.Path}/{result.Id}", result); + } + + /// Updates an application. + /// + [HttpPut("{id}", Name = "Order_Put")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PutAsync(int id, OrderDto body) + { + ValidationHelper.Validate(body.Id == id, nameof(body.Id), ValidationHelper.KeyDoesNotMatchRoute); + + var result = await _orderService.UpdateAsync(UserId, body); + + return Ok(result); + } + } +} diff --git a/Store.Web/Controllers/Api/ProductController.cs b/Store.Web/Controllers/Api/ProductController.cs new file mode 100644 index 0000000..e168d2b --- /dev/null +++ b/Store.Web/Controllers/Api/ProductController.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Store.Common.Contracts; +using Store.Common.Validation; +using Store.Services; +using Store.Services.Contracts.Product; +using Store.Web.Framework; + +namespace Store.Web.Controllers.Api +{ + /// Functionality relating to store management. + /// + [ApiController] + [ApiVersion("1")] + [Route("api/v{version:apiVersion}/product")] + public class ProductController : StoreApiController + { + private readonly IProductService _productService; + + public ProductController(IProductService productService) + { + _productService = productService; + } + + /// Delete the specified Product. + /// The Id of the Product to be deleted. + [HttpDelete("{id}", Name = "Product_Delete")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public async Task DeleteAsync(int id) + { + var result = await _productService.DeleteAsync(UserId, id); + + if (result == null) + { + return NotFound(); + } + + return NoContent(); + } + + /// Get a an Product by Id. + [HttpGet("{id}", Name = "Product_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task> GetAsync(int id) + { + var result = await _productService.GetAsync(UserId, id); + + if (result == null) + { + return NotFound(); + } + + return Ok(result); + } + + /// Get all Products. + [HttpGet("", Name = "Products_Get")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(ActionResult))] + public async Task>> GetAsync([FromQuery] PagingOptions pagingOptions) + { + var result = await _productService.GetAsync(UserId, pagingOptions); + + return Ok(result); + } + + /// Creates an Product. + /// The Product to be created. + [HttpPost("{id}", Name = "Product_Post")] + [ProducesResponseType((int)HttpStatusCode.Created)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PostAsync(ProductDto body) + { + var result = await _productService.AddAsync(UserId, body); + + if (result == null) + { + return BadRequest(); + } + + return Created($"{HttpContext.Request.Path}/{result.Id}", result); + } + + /// Updates an application. + /// + [HttpPut("{id}", Name = "Product_Put")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task> PutAsync(int id, ProductDto body) + { + ValidationHelper.Validate(body.Id == id, nameof(body.Id), ValidationHelper.KeyDoesNotMatchRoute); + + var result = await _productService.UpdateAsync(UserId, body); + + return Ok(result); + } + } +} diff --git a/Store.Web/Controllers/Mvc/HomeController.cs b/Store.Web/Controllers/Mvc/HomeController.cs index 5d51f2e..05d1138 100644 --- a/Store.Web/Controllers/Mvc/HomeController.cs +++ b/Store.Web/Controllers/Mvc/HomeController.cs @@ -1,11 +1,10 @@ using Microsoft.AspNetCore.Mvc; -using Store.Web.Framework; // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 namespace Store.Web.Controllers.Mvc { - public class HomeController : StoreMvcController + public class HomeController : Controller { // GET: // public IActionResult Index() diff --git a/Store.Web/Controllers/Mvc/StatusController.cs b/Store.Web/Controllers/Mvc/StatusController.cs index 9be00ff..b22378e 100644 --- a/Store.Web/Controllers/Mvc/StatusController.cs +++ b/Store.Web/Controllers/Mvc/StatusController.cs @@ -1,23 +1,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Store.Web.Framework; namespace Store.Web.Controllers.Mvc { - public class StatusController : StoreMvcController + public class StatusController : Controller { - // GET: Status - public ActionResult Index() - { - return View(); - } - - // GET: Status/Details/5 - public ActionResult Details(int id) - { - return View(); - } - // GET: Status/Create public ActionResult Create() { @@ -41,20 +28,20 @@ public ActionResult Create(IFormCollection collection) } } - // GET: Status/Edit/5 - public ActionResult Edit(int id) + // GET: Status/Delete/5 + public ActionResult Delete(int id) { return View(); } - // POST: Status/Edit/5 + // POST: Status/Delete/5 [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Edit(int id, IFormCollection collection) + public ActionResult Delete(int id, IFormCollection collection) { try { - // TODO: Add update logic here + // TODO: Add delete logic here return RedirectToAction(nameof(Index)); } @@ -64,20 +51,26 @@ public ActionResult Edit(int id, IFormCollection collection) } } - // GET: Status/Delete/5 - public ActionResult Delete(int id) + // GET: Status/Details/5 + public ActionResult Details(int id) { return View(); } - // POST: Status/Delete/5 + // GET: Status/Edit/5 + public ActionResult Edit(int id) + { + return View(); + } + + // POST: Status/Edit/5 [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Delete(int id, IFormCollection collection) + public ActionResult Edit(int id, IFormCollection collection) { try { - // TODO: Add delete logic here + // TODO: Add update logic here return RedirectToAction(nameof(Index)); } @@ -86,5 +79,11 @@ public ActionResult Delete(int id, IFormCollection collection) return View(); } } + + // GET: Status + public ActionResult Index() + { + return View(); + } } } diff --git a/Store.Web/Program.cs b/Store.Web/Program.cs index ef10139..e376e91 100644 --- a/Store.Web/Program.cs +++ b/Store.Web/Program.cs @@ -9,11 +9,11 @@ namespace Store.Web public class Program { /// Represents start-up configuration values. - /// The used to retrieve configuration parameters. + /// The used to retrieve configuration parameters. public static IConfiguration Configuration { get; } = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "local"}.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.json", false, true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "local"}.json", true, true) .AddEnvironmentVariables() .Build(); diff --git a/Store.Web/Properties/launchSettings.json b/Store.Web/Properties/launchSettings.json index b8f8935..4ff5c38 100644 --- a/Store.Web/Properties/launchSettings.json +++ b/Store.Web/Properties/launchSettings.json @@ -1,14 +1,22 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, + "windowsAuthentication": false, + "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:60966", "sslPort": 44327 } }, "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, "Swagger": { "commandName": "IISExpress", "launchBrowser": true, diff --git a/Store.Web/Startup.cs b/Store.Web/Startup.cs index fb2cfe4..a1ae7b1 100644 --- a/Store.Web/Startup.cs +++ b/Store.Web/Startup.cs @@ -22,8 +22,8 @@ public Startup(IConfiguration configuration) public IConfiguration Configuration { get; set; } /// Called on application start-up. - /// The . - /// The . + /// The . + /// The . /// The hosting environment. /// The API version description provider. public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider, IHostingEnvironment env, IApiVersionDescriptionProvider apiVersionDescriptionProvider) @@ -42,10 +42,7 @@ public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider, app.UseHttpsRedirection(); app.UseSwagger(Configuration, apiVersionDescriptionProvider); - app.UseMvc(routes => - { - routes.MapRoute(name: "Default", template: "{controller=Home}/{action=Index}/{id?}"); - }); + app.UseMvc(routes => { routes.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}"); }); } /// Configures the services with the IoC container. diff --git a/Store.Web/Store.Web.csproj b/Store.Web/Store.Web.csproj index 162b088..9404529 100644 --- a/Store.Web/Store.Web.csproj +++ b/Store.Web/Store.Web.csproj @@ -13,7 +13,7 @@ ..\Store.ruleset - C:\Users\Mel\OneDrive\Projects\Pluralsight\test-data-generation\Source\Code\Store.Web\Store.Web.xml + C:\Users\Mel\OneDrive\Projects\Pluralsight\test-data-generation\Source\Demo\Store.Web\Store.Web.xml 1591;1701;1702 @@ -22,8 +22,8 @@ - + diff --git a/Store.Web/Store.Web.xml b/Store.Web/Store.Web.xml index 1776ae0..2f24156 100644 --- a/Store.Web/Store.Web.xml +++ b/Store.Web/Store.Web.xml @@ -12,6 +12,9 @@ Delete the specified Address. The Id of the Address to be deleted. + + Get all Addresses. + Get a an Address by Id. @@ -23,6 +26,72 @@ Updates an application. + + Functionality relating to store management. + + + + Delete the specified Category. + The Id of the Category to be deleted. + + + Get all Categories. + + + Get a Category by Id. + + + Creates a Category. + The Category to be created. + + + Updates a Category. + + + + Functionality relating to store management. + + + + Delete the specified Order. + The Id of the Order to be deleted. + + + Get all Orders. + + + Get a an Order by Id. + + + Creates an Order. + The Order to be created. + + + Updates an application. + + + + Functionality relating to store management. + + + + Delete the specified Product. + The Id of the Product to be deleted. + + + Get a an Product by Id. + + + Get all Products. + + + Creates an Product. + The Product to be created. + + + Updates an application. + + Functionality relating to API status reporting. @@ -58,12 +127,12 @@ Represents start-up configuration values. - The used to retrieve configuration parameters. + The used to retrieve configuration parameters. Called on application start-up. - The . - The . + The . + The . The hosting environment. The API version description provider. diff --git a/Store.Web/Views/Home/Index.cshtml b/Store.Web/Views/Home/Index.cshtml index 3d0f0c4..6af1c1e 100644 --- a/Store.Web/Views/Home/Index.cshtml +++ b/Store.Web/Views/Home/Index.cshtml @@ -1,7 +1,5 @@ - -@{ +@{ ViewData["Title"] = "Index"; } -

Index

- +

Index

\ No newline at end of file diff --git a/Store.Web/Views/Shared/_CookieConsentPartial.cshtml b/Store.Web/Views/Shared/_CookieConsentPartial.cshtml index bbfbb09..8c2fbee 100644 --- a/Store.Web/Views/Shared/_CookieConsentPartial.cshtml +++ b/Store.Web/Views/Shared/_CookieConsentPartial.cshtml @@ -1,5 +1,4 @@ @using Microsoft.AspNetCore.Http.Features - @{ var consentFeature = Context.Features.Get(); var showBanner = !consentFeature?.CanTrack ?? false; @@ -17,7 +16,9 @@ - + + + } \ No newline at end of file diff --git a/Store.Web/Views/Shared/_Layout.cshtml b/Store.Web/Views/Shared/_Layout.cshtml index d36319c..609fb99 100644 --- a/Store.Web/Views/Shared/_Layout.cshtml +++ b/Store.Web/Views/Shared/_Layout.cshtml @@ -1,74 +1,80 @@  - - + + @ViewData["Title"] - WebApplicationAspNetCore - - + + - + asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"/> + - - + -
- @RenderBody() -
-
-

© 2018 - WebApplicationAspNetCore

-
-
+
+ @RenderBody() +
+
+

© 2018 - WebApplicationAspNetCore

+
+
- - - - - - - - - - + + + + + + + + + + - @RenderSection("Scripts", required: false) +@RenderSection("Scripts", false) \ No newline at end of file diff --git a/Store.Web/Views/Status/Index.cshtml b/Store.Web/Views/Status/Index.cshtml index cb5e716..bc704dd 100644 --- a/Store.Web/Views/Status/Index.cshtml +++ b/Store.Web/Views/Status/Index.cshtml @@ -5,4 +5,4 @@ Layout = "_Layout"; } -

title

+

title

\ No newline at end of file diff --git a/Store.Web/appsettings.Development.json b/Store.Web/appsettings.Development.json index e203e94..f999bc2 100644 --- a/Store.Web/appsettings.Development.json +++ b/Store.Web/appsettings.Development.json @@ -6,4 +6,4 @@ "Microsoft": "Information" } } -} +} \ No newline at end of file diff --git a/Store.Web/appsettings.json b/Store.Web/appsettings.json index d4e154b..0093c23 100644 --- a/Store.Web/appsettings.json +++ b/Store.Web/appsettings.json @@ -8,4 +8,4 @@ } }, "AllowedHosts": "*" -} +} \ No newline at end of file diff --git a/Store.sln b/Store.sln index 7d3c59c..bd00503 100644 --- a/Store.sln +++ b/Store.sln @@ -18,17 +18,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A387770C-C1E3-475F-8CCE-FFDDD71BEF8C}" - ProjectSection(SolutionItems) = preProject - _ReadMe.md = _ReadMe.md - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Store.Web", "Store.Web\Store.Web.csproj", "{1675F995-EAA9-4E26-A18F-AF6ABA53B34E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Store.Common", "Store.Common\Store.Common.csproj", "{34D29AB6-D326-47A9-97E5-133A22605805}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Tests.Unit", "Store.Tests.Unit\Store.Tests.Unit.csproj", "{5FAA4321-F452-4D39-89F2-FF8F0FF607B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Store.Tests.Integration", "..\Demo\Store.Tests.Integration\Store.Tests.Integration.csproj", "{9AA8646C-829E-430E-9A69-97C4D782AB0B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Tests.Integration", "Store.Tests.Integration\Store.Tests.Integration.csproj", "{FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Store.Tests.Unit", "..\Demo\Store.Tests.Unit\Store.Tests.Unit.csproj", "{3A80D79A-37A4-4EEB-8F0E-6F0631A6C360}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -52,21 +49,21 @@ Global {34D29AB6-D326-47A9-97E5-133A22605805}.Debug|Any CPU.Build.0 = Debug|Any CPU {34D29AB6-D326-47A9-97E5-133A22605805}.Release|Any CPU.ActiveCfg = Release|Any CPU {34D29AB6-D326-47A9-97E5-133A22605805}.Release|Any CPU.Build.0 = Release|Any CPU - {5FAA4321-F452-4D39-89F2-FF8F0FF607B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FAA4321-F452-4D39-89F2-FF8F0FF607B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FAA4321-F452-4D39-89F2-FF8F0FF607B2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FAA4321-F452-4D39-89F2-FF8F0FF607B2}.Release|Any CPU.Build.0 = Release|Any CPU - {FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD}.Release|Any CPU.Build.0 = Release|Any CPU + {9AA8646C-829E-430E-9A69-97C4D782AB0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AA8646C-829E-430E-9A69-97C4D782AB0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AA8646C-829E-430E-9A69-97C4D782AB0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AA8646C-829E-430E-9A69-97C4D782AB0B}.Release|Any CPU.Build.0 = Release|Any CPU + {3A80D79A-37A4-4EEB-8F0E-6F0631A6C360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A80D79A-37A4-4EEB-8F0E-6F0631A6C360}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A80D79A-37A4-4EEB-8F0E-6F0631A6C360}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A80D79A-37A4-4EEB-8F0E-6F0631A6C360}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {5FAA4321-F452-4D39-89F2-FF8F0FF607B2} = {A387770C-C1E3-475F-8CCE-FFDDD71BEF8C} - {FFC6C852-E62B-47E0-ADA0-B2DBD243F4FD} = {A387770C-C1E3-475F-8CCE-FFDDD71BEF8C} + {9AA8646C-829E-430E-9A69-97C4D782AB0B} = {A387770C-C1E3-475F-8CCE-FFDDD71BEF8C} + {3A80D79A-37A4-4EEB-8F0E-6F0631A6C360} = {A387770C-C1E3-475F-8CCE-FFDDD71BEF8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6B85CA2A-EB52-43D1-94F5-EDFF3B06C04D}