diff --git a/exercise.pizzashopapi/DTO/AddOrderDTO.cs b/exercise.pizzashopapi/DTO/AddOrderDTO.cs new file mode 100644 index 0000000..6d6a031 --- /dev/null +++ b/exercise.pizzashopapi/DTO/AddOrderDTO.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class AddOrderDTO + { + public int CustomerId { get; set; } + + public int PizzaId { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/AddToppingDTO.cs b/exercise.pizzashopapi/DTO/AddToppingDTO.cs new file mode 100644 index 0000000..b216f62 --- /dev/null +++ b/exercise.pizzashopapi/DTO/AddToppingDTO.cs @@ -0,0 +1,8 @@ + +namespace exercise.pizzashopapi.DTO +{ + public class AddToppingDTO + { + public string Type { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/CustomerNoListOrderDTO.cs b/exercise.pizzashopapi/DTO/CustomerNoListOrderDTO.cs new file mode 100644 index 0000000..30e15d6 --- /dev/null +++ b/exercise.pizzashopapi/DTO/CustomerNoListOrderDTO.cs @@ -0,0 +1,12 @@ +using exercise.pizzashopapi.Models; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.DTO +{ + public class CustomerNoListOrderDTO + { + public int Id { get; set; } + public string? Name { get; set; } + + } +} diff --git a/exercise.pizzashopapi/DTO/OrderDTO.cs b/exercise.pizzashopapi/DTO/OrderDTO.cs new file mode 100644 index 0000000..5fdb334 --- /dev/null +++ b/exercise.pizzashopapi/DTO/OrderDTO.cs @@ -0,0 +1,20 @@ +using exercise.pizzashopapi.Models; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.DTO +{ + public class OrderDTO + { + public int Id { get; set; } + + public int CustomerId { get; set; } + + public int PizzaId { get; set; } + + public CustomerNoListOrderDTO Customer { get; set; } + + public PizzaNoListOrderDTO Pizza { get; set; } + + public string OrderStatus { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/OrderToppingDTO.cs b/exercise.pizzashopapi/DTO/OrderToppingDTO.cs new file mode 100644 index 0000000..f167151 --- /dev/null +++ b/exercise.pizzashopapi/DTO/OrderToppingDTO.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class OrderToppingDTO + { + public int OrderId { get; set; } + public int ToppingId { get; set; } + } + +} diff --git a/exercise.pizzashopapi/DTO/PizzaDTO.cs b/exercise.pizzashopapi/DTO/PizzaDTO.cs new file mode 100644 index 0000000..a2f13ee --- /dev/null +++ b/exercise.pizzashopapi/DTO/PizzaDTO.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO +{ + public class PizzaDTO + { + public string Name { get; set; } + public decimal Price { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/PizzaNoListOrderDTO.cs b/exercise.pizzashopapi/DTO/PizzaNoListOrderDTO.cs new file mode 100644 index 0000000..4cf85e0 --- /dev/null +++ b/exercise.pizzashopapi/DTO/PizzaNoListOrderDTO.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class PizzaNoListOrderDTO + { + public int Id { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/TopppingDTO.cs b/exercise.pizzashopapi/DTO/TopppingDTO.cs new file mode 100644 index 0000000..a0974b5 --- /dev/null +++ b/exercise.pizzashopapi/DTO/TopppingDTO.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class ToppingDTO + { + public int Id { get; set; } + public string Type { get; set; } + } + +} diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..d323e20 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -24,5 +24,8 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public DbSet Pizzas { get; set; } public DbSet Customers { get; set; } public DbSet Orders { get; set; } + public DbSet Toppings { get; set; } + public DbSet OrderToppings { get; set; } + } } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..5ec653d 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -1,31 +1,75 @@ using exercise.pizzashopapi.Models; +using System; +using System.Collections.Generic; +using System.Linq; namespace exercise.pizzashopapi.Data { public static class Seeder { + private static readonly Random Random = new Random(); + private static readonly List OrderStatuses = new List + { + "Preparing", "Baking", "Quality Check", "Out for Delivery", "Delivered" + }; + public async static void SeedPizzaShopApi(this WebApplication app) { - using(var db = new DataContext()) + using (var db = new DataContext()) { - if(!db.Customers.Any()) + // Seed Customers + if (!db.Customers.Any()) { - db.Add(new Customer() { Name="Nigel" }); + db.Add(new Customer() { Name = "Nigel" }); db.Add(new Customer() { Name = "Dave" }); await db.SaveChangesAsync(); } - if(!db.Pizzas.Any()) + + // Seed Pizzas + if (!db.Pizzas.Any()) { - db.Add(new Pizza() { Name = "Cheese & Pineapple" }); - db.Add(new Pizza() { Name = "Vegan Cheese Tastic" }); + db.Add(new Pizza() { Name = "Cheese & Pineapple", Price = 150 }); + db.Add(new Pizza() { Name = "Vegan Cheese Tastic", Price = 120 }); await db.SaveChangesAsync(); + } + // Seed Toppings + if (!db.Toppings.Any()) + { + db.Add(new Toppings() { Type = "Cheese" }); + db.Add(new Toppings() { Type = "Pineapple" }); + await db.SaveChangesAsync(); } - //order data - if(1==1) + // Seed Orders + if (!db.Orders.Any()) { + db.Add(new Order() + { + Id = 1, + CustomerId = 1, + PizzaId = 1, + OrderToppings = new List { new OrderTopping { ToppingId = 2 } }, + OrderStatus = OrderStatuses[Random.Next(OrderStatuses.Count)] + }); + db.Add(new Order() + { + Id = 2, + CustomerId = 2, + PizzaId = 2, + OrderToppings = new List { new OrderTopping { ToppingId = 1 } }, + OrderStatus = OrderStatuses[Random.Next(OrderStatuses.Count)] + }); + + await db.SaveChangesAsync(); + } + + + if (!db.OrderToppings.Any()) + { + db.Add(new OrderTopping() { Id = 1, OrderId = 1, ToppingId = 1 }); + db.Add(new OrderTopping() { Id = 2, OrderId = 2, ToppingId = 2 }); await db.SaveChangesAsync(); } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..0662e15 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,5 +1,9 @@ -using exercise.pizzashopapi.Repository; +using AutoMapper; +using exercise.pizzashopapi.DTO; +using exercise.pizzashopapi.Models; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using workshop.wwwapi.Repository; namespace exercise.pizzashopapi.EndPoints { @@ -7,9 +11,146 @@ public static class PizzaShopApi { public static void ConfigurePizzaShopApi(this WebApplication app) { - + var pizza = app.MapGroup("shop"); + + pizza.MapGet("/pizza", GetPizzas); + pizza.MapGet("/order", GetOrders); + pizza.MapGet("/orderByCustomer{id}", GetOrderByCustomerId); + + pizza.MapPost("/orderforCustomer", AddOrderForCustomer); + pizza.MapPost("/pizza", AddPizza); + pizza.MapPost("/topping", AddTopping); + + pizza.MapPut("/updateOrder{id}", UpdateOrder); + pizza.MapPut("/updatePizza{id}", UpdatePizza); + + pizza.MapDelete("/deleteOrder{id}", DeleteOrder); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetPizzas(IRepository repository, IMapper mapper) + { + var pizzas = await repository.GetWithNestedIncludes(query => + query.Include(p => p.Orders) + .ThenInclude(a => a.Customer)); + + var response = mapper.Map>(pizzas); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetOrders(IRepository repository, IMapper mapper) + { + var orders = await repository.GetWithNestedIncludes(query => + query.Include(o => o.Customer) + .Include(o => o.Pizza)); + + var response = mapper.Map>(orders); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetOrderByCustomerId(IRepository repository, IMapper mapper, int id) + { + var orders = await repository.GetWithNestedIncludes(query => + query.Where(o => o.CustomerId == id) + .Include(o => o.Pizza).Include(o => o.Customer)); + + var response = mapper.Map>(orders); + return TypedResults.Ok(response); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddOrderForCustomer( + IRepository repository, + IRepository customerRepo, + IRepository pizzaRepo, + IMapper mapper, + int customerId, + int pizzaId) + { + var customer = await customerRepo.GetById(customerId); + var pizza = await pizzaRepo.GetById(pizzaId); + + if (customer == null || pizza == null) + { + return TypedResults.BadRequest("Invalid customer or pizza ID"); + } + + var order = new Order + { + CustomerId = customerId, + PizzaId = pizzaId, + Customer = customer, + Pizza = pizza, + OrderStatus = OrderStatuses[Random.Next(OrderStatuses.Count)] + }; + + await repository.Add(order); + var savedOrder = await repository.GetById(order.Id); + + if (savedOrder == null) + { + return TypedResults.Problem("Order was not saved correctly."); + } + + var response = mapper.Map(savedOrder); + return TypedResults.Created($"/shop/order/{savedOrder.Id}", response); + } + + + private static readonly Random Random = new Random(); + private static readonly List OrderStatuses = new List + { + "Preparing", "Baking", "Quality Check", "Out for Delivery", "Delivered" + }; + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddPizza(IRepository repository, IMapper mapper, PizzaDTO pizzaDto) + { + var pizza = mapper.Map(pizzaDto); + await repository.Add(pizza); + return TypedResults.Created($"/shop/pizza/{pizza.Id}", pizza); + } + + [ProducesResponseType(StatusCodes.Status201Created)] + public static async Task AddTopping(IRepository repository, IMapper mapper, AddToppingDTO toppingDto) + { + var topping = mapper.Map(toppingDto); + await repository.Add(topping); + return TypedResults.Created($"/shop/topping/{topping.Id}", topping); } - + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task UpdateOrder(IRepository repository, IMapper mapper, int id, AddOrderDTO orderDto) + { + var existingOrder = await repository.GetById(id); + if (existingOrder == null) return TypedResults.NotFound(); + + mapper.Map(orderDto, existingOrder); + await repository.Update(existingOrder); + return TypedResults.Ok(existingOrder); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task UpdatePizza(IRepository repository, IMapper mapper, int id, PizzaNoListOrderDTO pizzaDto) + { + var existingPizza = await repository.GetById(id); + if (existingPizza == null) return TypedResults.NotFound(); + + mapper.Map(pizzaDto, existingPizza); + await repository.Update(existingPizza); + return TypedResults.Ok(existingPizza); + } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + public static async Task DeleteOrder(IRepository repository, int id) + { + var existingOrder = await repository.GetById(id); + if (existingOrder == null) return TypedResults.NotFound(); + + await repository.Delete(existingOrder); + return TypedResults.NoContent(); + } } } diff --git a/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.Designer.cs b/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.Designer.cs new file mode 100644 index 0000000..032bcf3 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.Designer.cs @@ -0,0 +1,198 @@ +// +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("20250202213744_InitialMigration")] + partial class InitialMigration + { + /// + 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"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("customer"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer") + .HasColumnName("customer_id"); + + b.Property("OrderStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("integer") + .HasColumnName("order_id"); + + b.Property("ToppingId") + .HasColumnType("integer") + .HasColumnName("topping_id"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ToppingId"); + + b.ToTable("toppingOrder"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + 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("pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Toppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + 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("Orders") + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Toppings", "Topping") + .WithMany("ToppingOrders") + .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("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Toppings", b => + { + b.Navigation("ToppingOrders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.cs b/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.cs new file mode 100644 index 0000000..9c831e1 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250202213744_InitialMigration.cs @@ -0,0 +1,147 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "customer", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_customer", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "pizza", + 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_pizza", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "toppings", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + type = table.Column(type: "text", 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), + customer_id = table.Column(type: "integer", nullable: false), + pizza_id = table.Column(type: "integer", nullable: false), + OrderStatus = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_customer_customer_id", + column: x => x.customer_id, + principalTable: "customer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Orders_pizza_pizza_id", + column: x => x.pizza_id, + principalTable: "pizza", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "toppingOrder", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + topping_id = table.Column(type: "integer", nullable: false), + order_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_toppingOrder", x => x.Id); + table.ForeignKey( + name: "FK_toppingOrder_Orders_order_id", + column: x => x.order_id, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_toppingOrder_toppings_topping_id", + column: x => x.topping_id, + principalTable: "toppings", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + 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_toppingOrder_order_id", + table: "toppingOrder", + column: "order_id"); + + migrationBuilder.CreateIndex( + name: "IX_toppingOrder_topping_id", + table: "toppingOrder", + column: "topping_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "toppingOrder"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "toppings"); + + migrationBuilder.DropTable( + name: "customer"); + + migrationBuilder.DropTable( + name: "pizza"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..707ef68 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,195 @@ +// +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"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("customer"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer") + .HasColumnName("customer_id"); + + b.Property("OrderStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("integer") + .HasColumnName("order_id"); + + b.Property("ToppingId") + .HasColumnType("integer") + .HasColumnName("topping_id"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ToppingId"); + + b.ToTable("toppingOrder"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + 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("pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Toppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + 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("Orders") + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Toppings", "Topping") + .WithMany("ToppingOrders") + .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("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Toppings", b => + { + b.Navigation("ToppingOrders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Models/Customer.cs b/exercise.pizzashopapi/Models/Customer.cs index 2ca83bd..84e806c 100644 --- a/exercise.pizzashopapi/Models/Customer.cs +++ b/exercise.pizzashopapi/Models/Customer.cs @@ -1,10 +1,17 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { + [Table("customer")] public class Customer { + [Key] public int Id { get; set; } - public string Name { get; set; } + + [Column("name")] + public string? Name { get; set; } + + public List Orders { get; set; } = new List(); } } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..6f76a8b 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -1,10 +1,27 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { public class Order { - - + [Key] + public int Id { get; set; } + + [Column("customer_id")] + [ForeignKey("customer")] + public int CustomerId { get; set; } + + [Column("pizza_id")] + [ForeignKey("pizza")] + public int PizzaId { get; set; } + + public Customer Customer { get; set; } + + public Pizza Pizza { get; set; } + + public List OrderToppings { get; set; } = new List(); + + public string OrderStatus { get; set; } } } diff --git a/exercise.pizzashopapi/Models/OrderTopping.cs b/exercise.pizzashopapi/Models/OrderTopping.cs new file mode 100644 index 0000000..2991f6d --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderTopping.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.Models +{ + [Table("toppingOrder")] + public class OrderTopping + { + + [Key] + public int Id { get; set; } + + [Column("topping_id")] + [ForeignKey("toppings")] + public int ToppingId { get; set; } + + + [Column("order_id")] + [ForeignKey("order")] + public int OrderId { get; set; } + + + public Toppings Topping { get; set; } + public Order Order { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/Pizza.cs b/exercise.pizzashopapi/Models/Pizza.cs index 5c085ec..0982bd9 100644 --- a/exercise.pizzashopapi/Models/Pizza.cs +++ b/exercise.pizzashopapi/Models/Pizza.cs @@ -1,12 +1,20 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.pizzashopapi.Models { - + [Table("pizza")] public class Pizza - { + { + [Key] public int Id { get; set; } + + [Column("name")] public string Name { get; set; } + + [Column("price")] public decimal Price { get; set; } + + public List Orders { get; set; } = new List(); } } \ No newline at end of file diff --git a/exercise.pizzashopapi/Models/Toppings.cs b/exercise.pizzashopapi/Models/Toppings.cs new file mode 100644 index 0000000..5cd304b --- /dev/null +++ b/exercise.pizzashopapi/Models/Toppings.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace exercise.pizzashopapi.Models +{ + [Table("toppings")] + public class Toppings + { + [Key] + public int Id { get; set; } + + + [Column("type")] + public string Type { get; set; } + + public List ToppingOrders { get; set; } = new List(); + + + } +} diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index c04a440..cb178e9 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -1,14 +1,19 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.EndPoints; -using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Models; +using workshop.wwwapi.Repository; var builder = WebApplication.CreateBuilder(args); // Add services to the container. - builder.Services.AddControllers(); -builder.Services.AddScoped(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); builder.Services.AddDbContext(); +builder.Services.AddAutoMapper(typeof(Program)); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index b114ea8..d864a74 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -1,11 +1,22 @@ -using exercise.pizzashopapi.Models; +using System.Linq.Expressions; -namespace exercise.pizzashopapi.Repository +namespace workshop.wwwapi.Repository { - public interface IRepository + public interface IRepository { - Task> GetOrdersByCustomer(int id); - + Task> Get(); + Task GetById(int id); + + Task Add(T entity); + Task Update(T entity); + Task Delete(object id); + Task Save(); + Task> GetWithIncludes(params Expression>[] includes); + Task GetByIdWithIncludes(int id, params Expression>[] includes); + Task> GetWithNestedIncludes(params Func, IQueryable>[] includeActions); + + IQueryable GetQuery(); + } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index e109fce..dac0ea9 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -1,14 +1,96 @@ -using exercise.pizzashopapi.Data; -using exercise.pizzashopapi.Models; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using exercise.pizzashopapi.Data; +using Microsoft.EntityFrameworkCore; -namespace exercise.pizzashopapi.Repository + +namespace workshop.wwwapi.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 db) + { + _db = db; + _table = _db.Set(); + } + + public async Task Add(T entity) + { + _table.Add(entity); + await _db.SaveChangesAsync(); + return entity; + } + + public async Task Delete(object id) + { + T existing = await _table.FindAsync(id); + _table.Remove(existing); + await _db.SaveChangesAsync(); + return existing; + } + + public async Task> Get() + { + return await _table.ToListAsync(); + } + + 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 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 GetByIdWithIncludes(int id, params Expression>[] includes) + { + IQueryable query = _table; + + foreach (var include in includes) + { + query = query.Include(include); + } + + return await query.FirstOrDefaultAsync(entity => EF.Property(entity, "Id") == id); + } + + public async Task Save() + { + await _db.SaveChangesAsync(); + } + + public async Task Update(T entity) + { + _table.Attach(entity); + _db.Entry(entity).State = EntityState.Modified; + await _db.SaveChangesAsync(); + return entity; + } + + public IQueryable GetQuery() { - throw new NotImplementedException(); + return _table; } } } diff --git a/exercise.pizzashopapi/Tools/Mapper.cs b/exercise.pizzashopapi/Tools/Mapper.cs new file mode 100644 index 0000000..187b945 --- /dev/null +++ b/exercise.pizzashopapi/Tools/Mapper.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using exercise.pizzashopapi.DTO; +using exercise.pizzashopapi.Models; +using System; +using System.Collections.Generic; + +namespace exercise.pizzashopapi.Tools +{ + public class Mapper : Profile + { + private static readonly Random Random = new Random(); + private static readonly List OrderStatuses = new List + { + "Preparing", "Baking", "Quality Check", "Out for Delivery", "Delivered" + }; + + public Mapper() + { + CreateMap() + .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)) + .ForMember(dest => dest.Pizza, opt => opt.MapFrom(src => src.Pizza)) + .ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.CustomerId)) + .ForMember(dest => dest.PizzaId, opt => opt.MapFrom(src => src.PizzaId)) + .ForMember(dest => dest.OrderStatus, opt => opt.MapFrom(src => src.OrderStatus ?? "Pending")); + + CreateMap() + .ForMember(dest => dest.OrderStatus, opt => opt.MapFrom(src => OrderStatuses[Random.Next(OrderStatuses.Count)])); + + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } + } +} diff --git a/exercise.pizzashopapi/exercise.pizzashopapi.csproj b/exercise.pizzashopapi/exercise.pizzashopapi.csproj index 624203b..c7d5933 100644 --- a/exercise.pizzashopapi/exercise.pizzashopapi.csproj +++ b/exercise.pizzashopapi/exercise.pizzashopapi.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -11,18 +11,19 @@ - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + +