From c5ef4110a28d08ca35b39aa12bf48e8ead0aa210 Mon Sep 17 00:00:00 2001 From: Miadog7Extra Date: Mon, 27 Jan 2025 15:48:59 +0100 Subject: [PATCH] Base working for core extension --- .../DTO/Request/OrderPost.cs | 11 + .../DTO/Response/CustomerDTO.cs | 8 + .../DTO/Response/OrderDTO.cs | 16 ++ .../DTO/Response/OrderProductDTO.cs | 8 + .../DTO/Response/OrderToppingDTO.cs | 8 + exercise.pizzashopapi/Data/DataContext.cs | 46 +++- exercise.pizzashopapi/Data/Seeder.cs | 49 ++-- .../EndPoints/PizzaShopApi.cs | 134 +++++++++- .../20250127114536_working.Designer.cs | 246 ++++++++++++++++++ .../Migrations/20250127114536_working.cs | 190 ++++++++++++++ .../Migrations/DataContextModelSnapshot.cs | 243 +++++++++++++++++ exercise.pizzashopapi/Models/Customer.cs | 8 +- exercise.pizzashopapi/Models/Order.cs | 19 +- exercise.pizzashopapi/Models/OrderProduct.cs | 16 ++ exercise.pizzashopapi/Models/OrderTopping.cs | 14 + exercise.pizzashopapi/Models/Pizza.cs | 11 +- exercise.pizzashopapi/Models/Product.cs | 18 ++ exercise.pizzashopapi/Models/Topping.cs | 18 ++ exercise.pizzashopapi/Program.cs | 24 +- .../Repository/IRepository.cs | 14 +- .../Repository/Repository.cs | 79 +++++- exercise.pizzashopapi/Tools/MappingProfile.cs | 30 +++ .../exercise.pizzashopapi.csproj | 13 +- 23 files changed, 1171 insertions(+), 52 deletions(-) create mode 100644 exercise.pizzashopapi/DTO/Request/OrderPost.cs create mode 100644 exercise.pizzashopapi/DTO/Response/CustomerDTO.cs create mode 100644 exercise.pizzashopapi/DTO/Response/OrderDTO.cs create mode 100644 exercise.pizzashopapi/DTO/Response/OrderProductDTO.cs create mode 100644 exercise.pizzashopapi/DTO/Response/OrderToppingDTO.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127114536_working.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127114536_working.cs create mode 100644 exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs create mode 100644 exercise.pizzashopapi/Models/OrderProduct.cs create mode 100644 exercise.pizzashopapi/Models/OrderTopping.cs create mode 100644 exercise.pizzashopapi/Models/Product.cs create mode 100644 exercise.pizzashopapi/Models/Topping.cs create mode 100644 exercise.pizzashopapi/Tools/MappingProfile.cs diff --git a/exercise.pizzashopapi/DTO/Request/OrderPost.cs b/exercise.pizzashopapi/DTO/Request/OrderPost.cs new file mode 100644 index 0000000..f959b54 --- /dev/null +++ b/exercise.pizzashopapi/DTO/Request/OrderPost.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.DTO.Request +{ + public class OrderPost + { + public int Quantity { get; set; } + public int PizzaId { get; set; } + public int CustomerId { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/Response/CustomerDTO.cs b/exercise.pizzashopapi/DTO/Response/CustomerDTO.cs new file mode 100644 index 0000000..e7b8200 --- /dev/null +++ b/exercise.pizzashopapi/DTO/Response/CustomerDTO.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO.Response +{ + public class CustomerDTO + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/Response/OrderDTO.cs b/exercise.pizzashopapi/DTO/Response/OrderDTO.cs new file mode 100644 index 0000000..07f7f71 --- /dev/null +++ b/exercise.pizzashopapi/DTO/Response/OrderDTO.cs @@ -0,0 +1,16 @@ +using exercise.pizzashopapi.Models; +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.DTO.Response +{ + public class OrderDTO + { + public int Id { get; set; } + public int Quantity { get; set; } + public Pizza Pizza { get; set; } + public CustomerDTO Customer { get; set; } + public List Toppings { get; set; } = new(); + public List Products { get; set; } = new(); + } +} diff --git a/exercise.pizzashopapi/DTO/Response/OrderProductDTO.cs b/exercise.pizzashopapi/DTO/Response/OrderProductDTO.cs new file mode 100644 index 0000000..781cc92 --- /dev/null +++ b/exercise.pizzashopapi/DTO/Response/OrderProductDTO.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO.Response +{ + public class OrderProductDTO + { + public int ProductId { get; set; } + public string ProductName { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/Response/OrderToppingDTO.cs b/exercise.pizzashopapi/DTO/Response/OrderToppingDTO.cs new file mode 100644 index 0000000..d50d2f2 --- /dev/null +++ b/exercise.pizzashopapi/DTO/Response/OrderToppingDTO.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO.Response +{ + public class OrderToppingDTO + { + public int ToppingId { get; set; } + public string ToppingName { get; set; } + } +} diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..7cefda5 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -5,24 +5,56 @@ namespace exercise.pizzashopapi.Data { public class DataContext : DbContext { - private string connectionString; - public DataContext() + public DataContext(DbContextOptions options) : base(options) { - var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString"); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(ot => new { ot.OrderId, ot.ToppingId }); + + modelBuilder.Entity() + .HasOne(ot => ot.Order) + .WithMany(o => o.Toppings) + .HasForeignKey(ot => ot.OrderId); + + modelBuilder.Entity() + .HasOne(ot => ot.Topping) + .WithMany() + .HasForeignKey(ot => ot.ToppingId); + + modelBuilder.Entity() + .HasKey(op => new { op.OrderId, op.ProductId }); + modelBuilder.Entity() + .HasOne(op => op.Order) + .WithMany(o => o.Products) + .HasForeignKey(op => op.OrderId); + + modelBuilder.Entity() + .HasOne(op => op.Product) + .WithMany() + .HasForeignKey(op => op.ProductId); + + base.OnModelCreating(modelBuilder); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql(connectionString); + { + if (!optionsBuilder.IsConfigured) + { + throw new InvalidOperationException("DbContext options are not configured."); + } //set primary of order? - //seed data? } public DbSet Pizzas { get; set; } public DbSet Customers { get; set; } public DbSet Orders { get; set; } + public DbSet OrdersTopping { get; set; } + public DbSet OrderProducts { get; set; } + public DbSet Products { get; set; } + public DbSet Toppings { get; set; } } } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..1625df9 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -4,30 +4,41 @@ namespace exercise.pizzashopapi.Data { public static class Seeder { - public async static void SeedPizzaShopApi(this WebApplication app) + public async static Task SeedPizzaShopApi(this WebApplication app) { - using(var db = new DataContext()) + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + if (!db.Customers.Any()) { - if(!db.Customers.Any()) - { - db.Add(new Customer() { Name="Nigel" }); - db.Add(new Customer() { Name = "Dave" }); - await db.SaveChangesAsync(); - } - if(!db.Pizzas.Any()) - { - db.Add(new Pizza() { Name = "Cheese & Pineapple" }); - db.Add(new Pizza() { Name = "Vegan Cheese Tastic" }); - await db.SaveChangesAsync(); + db.Add(new Customer() { Name = "Nigel" }); + db.Add(new Customer() { Name = "Dave" }); + db.Add(new Customer() { Name = "Petter" }); + await db.SaveChangesAsync(); + } - } + if (!db.Pizzas.Any()) + { + db.Add(new Pizza() { Name = "Cheese & Pineapple", Price = 95 }); + db.Add(new Pizza() { Name = "Vegan Cheese Tastic", Price = 90 }); + db.Add(new Pizza() { Name = "Diavola", Price = 100 }); + await db.SaveChangesAsync(); + } - //order data - if(1==1) - { + if (!db.Products.Any()) + { + db.Add(new Product() { Name = "Burger", Price = 110 }); + db.Add(new Product() { Name = "Drink", Price = 20 }); + db.Add(new Product() { Name = "Fries", Price = 40 }); + await db.SaveChangesAsync(); + } - await db.SaveChangesAsync(); - } + if (!db.Toppings.Any()) + { + db.Add(new Topping() { Name = "Beef", Price = 20 }); + db.Add(new Topping() { Name = "Onion", Price = 5 }); + db.Add(new Topping() { Name = "Fish", Price = 40 }); + await db.SaveChangesAsync(); } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..b4e42ed 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,5 +1,10 @@ -using exercise.pizzashopapi.Repository; +using AutoMapper; +using exercise.pizzashopapi.DTO.Request; +using exercise.pizzashopapi.DTO.Response; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository; using Microsoft.AspNetCore.Mvc; +using System; namespace exercise.pizzashopapi.EndPoints { @@ -7,9 +12,132 @@ public static class PizzaShopApi { public static void ConfigurePizzaShopApi(this WebApplication app) { - + var pizzaShop = app.MapGroup("pizzashop"); + + pizzaShop.MapGet("/pizza", GetPizzas); + pizzaShop.MapGet("/products", GetProducts); + pizzaShop.MapGet("/toppings", GetToppings); + pizzaShop.MapGet("/orderByCustomer{id}", GetOrdersByCustomerId); + + pizzaShop.MapPost("/order", AddOrder); + pizzaShop.MapPost("/orderProduct/{order_id}/{product_id}", AddProductToOrder); + pizzaShop.MapPost("/orderTopping/{order_id}/{topping_id}", AddToppingToOrder); + } + public static async Task GetPizzas(IRepository repository) + { + var pizzas = await repository.Get(); + + return TypedResults.Ok(pizzas); + } + public static async Task GetProducts(IRepository repository) + { + var products = await repository.Get(); + + return TypedResults.Ok(products); + } + public static async Task GetToppings(IRepository repository) + { + var toppings = await repository.Get(); + + return TypedResults.Ok(toppings); + } + + public static async Task GetOrdersByCustomerId(IRepository repository, int id, IMapper mapper) + { + var orders = await repository.GetOrdersByCustomer(id); + + var orderDTOs = mapper.Map>(orders); + + return TypedResults.Ok(orderDTOs); + } + + public static async Task AddOrder(IRepository repository, OrderPost model, IMapper mapper) + { + Order order = new Order() + { + Quantity = model.Quantity, + PizzaId = model.PizzaId, + CustomerId = model.CustomerId, + }; + await repository.Add(order); + + var orderDTOs = mapper.Map(order); + + return TypedResults.Created($"https://localhost:7010/orders/{order.Id}", orderDTOs); + } + public static async Task AddProductToOrder(IRepository orderProductRepository, + IRepository orderRepository, + IRepository productRepository, + int order_id, int product_id, + IMapper mapper) + { + var order = await orderRepository.GetById(order_id); + if (order == null) + { + return TypedResults.NotFound(); + } + + var product = await productRepository.GetById(product_id); + if (product == null) + { + return TypedResults.NotFound(); + } + + OrderProduct orderProduct = new OrderProduct + { + OrderId = order_id, + ProductId = product_id, + Order = order, + Product = product, + }; + + + await orderProductRepository.Add(orderProduct); + + order.Products.Add(orderProduct); + + await orderRepository.Update(order); + + var orderDTOs = mapper.Map(order); + + return TypedResults.Created($"https://localhost:7010/orders/{order.Id}", orderDTOs); } + public static async Task AddToppingToOrder(IRepository toppingProductRepository, + IRepository orderRepository, + IRepository toppingRepository, + int order_id, int topping_id, + IMapper mapper) + { + var order = await orderRepository.GetById(order_id); + if (order == null) + { + return TypedResults.NotFound(); + } + + var topping = await toppingRepository.GetById(topping_id); + if (topping == null) + { + return TypedResults.NotFound(); + } + + OrderTopping orderTopping = new OrderTopping + { + OrderId = order_id, + ToppingId = topping_id, + Order = order, + Topping = topping, + }; + - + await toppingProductRepository.Add(orderTopping); + + order.Toppings.Add(orderTopping); + + await orderRepository.Update(order); + + var orderDTOs = mapper.Map(order); + + return TypedResults.Created($"https://localhost:7010/orders/{order.Id}", orderDTOs); + } } } diff --git a/exercise.pizzashopapi/Migrations/20250127114536_working.Designer.cs b/exercise.pizzashopapi/Migrations/20250127114536_working.Designer.cs new file mode 100644 index 0000000..4f8fe06 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114536_working.Designer.cs @@ -0,0 +1,246 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using exercise.pizzashopapi.Data; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250127114536_working")] + partial class working + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer") + .HasColumnName("customer_id"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderProduct", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ProductId") + .HasColumnType("integer"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderProducts"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ToppingId") + .HasColumnType("integer"); + + b.HasKey("OrderId", "ToppingId"); + + b.HasIndex("ToppingId"); + + b.ToTable("OrdersTopping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Pizzas"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Toppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Pizza", "Pizza") + .WithMany() + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderProduct", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("Products") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("Toppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany() + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("Products"); + + b.Navigation("Toppings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127114536_working.cs b/exercise.pizzashopapi/Migrations/20250127114536_working.cs new file mode 100644 index 0000000..3a24b0a --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114536_working.cs @@ -0,0 +1,190 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class working : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "Pizzas", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: false), + price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Pizzas", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: false), + price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "Toppings", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: false), + price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Toppings", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + quantity = table.Column(type: "integer", nullable: false), + pizza_id = table.Column(type: "integer", nullable: false), + customer_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.id); + table.ForeignKey( + name: "FK_Orders_Customers_customer_id", + column: x => x.customer_id, + principalTable: "Customers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Orders_Pizzas_pizza_id", + column: x => x.pizza_id, + principalTable: "Pizzas", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderProducts", + columns: table => new + { + OrderId = table.Column(type: "integer", nullable: false), + ProductId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderProducts", x => new { x.OrderId, x.ProductId }); + table.ForeignKey( + name: "FK_OrderProducts_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderProducts_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrdersTopping", + columns: table => new + { + OrderId = table.Column(type: "integer", nullable: false), + ToppingId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrdersTopping", x => new { x.OrderId, x.ToppingId }); + table.ForeignKey( + name: "FK_OrdersTopping_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrdersTopping_Toppings_ToppingId", + column: x => x.ToppingId, + principalTable: "Toppings", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrderProducts_ProductId", + table: "OrderProducts", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_customer_id", + table: "Orders", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_pizza_id", + table: "Orders", + column: "pizza_id"); + + migrationBuilder.CreateIndex( + name: "IX_OrdersTopping_ToppingId", + table: "OrdersTopping", + column: "ToppingId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrderProducts"); + + migrationBuilder.DropTable( + name: "OrdersTopping"); + + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "Toppings"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "Pizzas"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..7e5c4e4 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,243 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using exercise.pizzashopapi.Data; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer") + .HasColumnName("customer_id"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderProduct", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ProductId") + .HasColumnType("integer"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderProducts"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ToppingId") + .HasColumnType("integer"); + + b.HasKey("OrderId", "ToppingId"); + + b.HasIndex("ToppingId"); + + b.ToTable("OrdersTopping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Pizzas"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.ToTable("Toppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Pizza", "Pizza") + .WithMany() + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderProduct", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("Products") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("Toppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany() + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("Products"); + + b.Navigation("Toppings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Models/Customer.cs b/exercise.pizzashopapi/Models/Customer.cs index 2ca83bd..9612485 100644 --- a/exercise.pizzashopapi/Models/Customer.cs +++ b/exercise.pizzashopapi/Models/Customer.cs @@ -1,10 +1,16 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { public class Customer { + [Key] + [Column("id")] public int Id { get; set; } + [Required] + [Column("name")] public string Name { get; set; } + public List Orders { get; set; } } } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..776416a 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -1,10 +1,23 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { public class Order { - - + [Key] + [Column("id")] + public int Id { get; set; } + [Column("quantity")] + public int Quantity { get; set; } + [Column("pizza_id")] + public int PizzaId { get; set; } + [Column("customer_id")] + public int CustomerId { get; set; } + public Pizza Pizza { get; set; } + public Customer Customer { get; set; } + public List Toppings { get; set; } = new(); + public List Products { get; set; } = new(); + } } diff --git a/exercise.pizzashopapi/Models/OrderProduct.cs b/exercise.pizzashopapi/Models/OrderProduct.cs new file mode 100644 index 0000000..af0fe67 --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderProduct.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.Models +{ + public class OrderProduct + { + [Key] + public int OrderId { get; set; } + + [Key] + public int ProductId { get; set; } + + public Order Order { get; set; } + public Product Product { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/OrderTopping.cs b/exercise.pizzashopapi/Models/OrderTopping.cs new file mode 100644 index 0000000..533d2ba --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderTopping.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.Models +{ + public class OrderTopping + { + [Key] + public int OrderId { get; set; } + [Key] + public int ToppingId { get; set; } + public Order Order { get; set; } + public Topping Topping { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/Pizza.cs b/exercise.pizzashopapi/Models/Pizza.cs index 5c085ec..09a3f7b 100644 --- a/exercise.pizzashopapi/Models/Pizza.cs +++ b/exercise.pizzashopapi/Models/Pizza.cs @@ -1,12 +1,19 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { public class Pizza - { + { + [Key] + [Column("id")] public int Id { get; set; } + [Required] + [Column("name")] public string Name { get; set; } + [Required] + [Column("price")] public decimal Price { get; set; } } } \ No newline at end of file diff --git a/exercise.pizzashopapi/Models/Product.cs b/exercise.pizzashopapi/Models/Product.cs new file mode 100644 index 0000000..843dcf8 --- /dev/null +++ b/exercise.pizzashopapi/Models/Product.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.Models +{ + public class Product + { + [Key] + [Column("id")] + public int Id { get; set; } + [Required] + [Column("name")] + public string Name { get; set; } + [Required] + [Column("price")] + public decimal Price { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/Topping.cs b/exercise.pizzashopapi/Models/Topping.cs new file mode 100644 index 0000000..2bef516 --- /dev/null +++ b/exercise.pizzashopapi/Models/Topping.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.Models +{ + public class Topping + { + [Key] + [Column("id")] + public int Id { get; set; } + [Required] + [Column("name")] + public string Name { get; set; } + [Required] + [Column("price")] + public decimal Price { get; set; } + } +} diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index c04a440..ce72ffc 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -1,18 +1,32 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.EndPoints; +using exercise.pizzashopapi.Models; using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Tools; +using Microsoft.EntityFrameworkCore; +using System.Diagnostics; var builder = WebApplication.CreateBuilder(args); // Add services to the container. - -builder.Services.AddControllers(); -builder.Services.AddScoped(); -builder.Services.AddDbContext(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddControllers(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped < IRepository, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); + +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString"))); + + +builder.Services.AddAutoMapper(typeof(Program)); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -30,6 +44,6 @@ app.ConfigurePizzaShopApi(); -app.SeedPizzaShopApi(); +await app.SeedPizzaShopApi(); app.Run(); diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index b114ea8..e4ec072 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -1,11 +1,19 @@ using exercise.pizzashopapi.Models; +using System.Linq.Expressions; namespace exercise.pizzashopapi.Repository { - public interface IRepository + public interface IRepository { + Task> Get(); + Task Add(T entity); + Task Update(T entity); + Task Delete(object id); + Task Save(); + Task GetById(int id); + Task> GetWithIncludes(params Expression>[] includes); + Task> GetWithNestedIncludes(params Func, IQueryable>[] includeActions); + IQueryable GetQueryable(); Task> GetOrdersByCustomer(int id); - - } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index e109fce..118f461 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -1,14 +1,87 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.Models; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; namespace exercise.pizzashopapi.Repository { - public class Repository : IRepository + public class Repository : IRepository where T : class { private DataContext _db; - public Task> GetOrdersByCustomer(int id) + private DbSet _table = null; + + public Repository(DataContext dataContext) + { + _db = dataContext; + _table = _db.Set(); + } + public async Task> Get() + { + return _table.ToList(); + } + public async Task Add(T entity) + { + _table.Add(entity); + await _db.SaveChangesAsync(); + return entity; + } + public async Task Update(T entity) + { + _table.Attach(entity); + _db.Entry(entity).State = EntityState.Modified; + _db.SaveChanges(); + return entity; + } + public async Task Delete(object id) + { + T entity = _table.Find(id); + _table.Remove(entity); + _db.SaveChanges(); + return entity; + } + public async Task GetById(int id) + { + return await _table.FindAsync(id); + } + public async Task> GetWithIncludes(params Expression>[] includes) + { + IQueryable query = _table; + foreach (var include in includes) + { + query = query.Include(include); + } + return await query.ToListAsync(); + } + public IQueryable GetQueryable() + { + return _table; + } + public async Task> GetWithNestedIncludes(params Func, IQueryable>[] includeActions) + { + IQueryable query = _table; + + foreach (var includeAction in includeActions) + { + query = includeAction(query); + } + + return await query.ToListAsync(); + } + public async Task Save() + { + await _db.SaveChangesAsync(); + } + public async Task> GetOrdersByCustomer(int id) { - throw new NotImplementedException(); + return await _db.Orders + .Where(o => o.CustomerId == id) + .Include(o => o.Pizza) + .Include(o => o.Customer) + .Include(o => o.Toppings) + .ThenInclude(ot => ot.Topping) + .Include(o => o.Products) + .ThenInclude(op => op.Product) + .ToListAsync(); } } } diff --git a/exercise.pizzashopapi/Tools/MappingProfile.cs b/exercise.pizzashopapi/Tools/MappingProfile.cs new file mode 100644 index 0000000..aed05ed --- /dev/null +++ b/exercise.pizzashopapi/Tools/MappingProfile.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using exercise.pizzashopapi.DTO.Response; +using exercise.pizzashopapi.Models; + +namespace exercise.pizzashopapi.Tools +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Pizza, opt => opt.MapFrom(src => src.Pizza)) + .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)) + .ForMember(dest => dest.Toppings, opt => opt.MapFrom(src => src.Toppings)) + .ForMember(dest => dest.Products, opt => opt.MapFrom(src => src.Products)); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.ToppingId, opt => opt.MapFrom(src => src.Topping.Id)) + .ForMember(dest => dest.ToppingName, opt => opt.MapFrom(src => src.Topping.Name)); + + CreateMap() + .ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.Product.Id)) + .ForMember(dest => dest.ProductName, opt => opt.MapFrom(src => src.Product.Name)); + + } + + } +} diff --git a/exercise.pizzashopapi/exercise.pizzashopapi.csproj b/exercise.pizzashopapi/exercise.pizzashopapi.csproj index 624203b..4822b0b 100644 --- a/exercise.pizzashopapi/exercise.pizzashopapi.csproj +++ b/exercise.pizzashopapi/exercise.pizzashopapi.csproj @@ -11,18 +11,19 @@ - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + +