From 4c5c245af9ba9eecfefa0834cab289fe2bbc526e Mon Sep 17 00:00:00 2001 From: Enock97 Date: Mon, 27 Jan 2025 14:04:21 +0100 Subject: [PATCH] + Finished core and last 2 extension exercises --- exercise.pizzashopapi/Data/DataContext.cs | 15 +- exercise.pizzashopapi/Data/Seeder.cs | 92 +++++++- .../EndPoints/PizzaShopApi.cs | 149 ++++++++++++- ...0250127095856_InitialMigration.Designer.cs | 206 ++++++++++++++++++ .../20250127095856_InitialMigration.cs | 171 +++++++++++++++ .../Migrations/DataContextModelSnapshot.cs | 203 +++++++++++++++++ .../Models/DTO/MenuItemDTO.cs | 11 + exercise.pizzashopapi/Models/DTO/OrderDTO.cs | 17 ++ .../Models/DTO/OrderMenuItemRequest.cs | 8 + .../Models/DeliveryDriver.cs | 8 + exercise.pizzashopapi/Models/MenuItem.cs | 10 + exercise.pizzashopapi/Models/Order.cs | 12 +- exercise.pizzashopapi/Models/OrderMenuItem.cs | 17 ++ exercise.pizzashopapi/Program.cs | 13 +- .../GenericRepositories/IRepository.cs | 13 ++ .../GenericRepositories/Repository.cs | 51 +++++ .../Repository/IRepository.cs | 11 - .../Repository/Repository.cs | 14 -- .../IMenuItemRepository.cs | 10 + .../IOrderMenuItemRepository.cs | 11 + .../SpecificRepositories/IOrderRepository.cs | 12 + .../MenuItemRepository.cs | 24 ++ .../OrderMenuItemRepository.cs | 49 +++++ .../SpecificRepositories/OrderRepository.cs | 49 +++++ 24 files changed, 1129 insertions(+), 47 deletions(-) create mode 100644 exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.cs create mode 100644 exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs create mode 100644 exercise.pizzashopapi/Models/DTO/MenuItemDTO.cs create mode 100644 exercise.pizzashopapi/Models/DTO/OrderDTO.cs create mode 100644 exercise.pizzashopapi/Models/DTO/OrderMenuItemRequest.cs create mode 100644 exercise.pizzashopapi/Models/DeliveryDriver.cs create mode 100644 exercise.pizzashopapi/Models/MenuItem.cs create mode 100644 exercise.pizzashopapi/Models/OrderMenuItem.cs create mode 100644 exercise.pizzashopapi/Repository/GenericRepositories/IRepository.cs create mode 100644 exercise.pizzashopapi/Repository/GenericRepositories/Repository.cs delete mode 100644 exercise.pizzashopapi/Repository/IRepository.cs delete mode 100644 exercise.pizzashopapi/Repository/Repository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/IMenuItemRepository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/IOrderMenuItemRepository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/IOrderRepository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/MenuItemRepository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/OrderMenuItemRepository.cs create mode 100644 exercise.pizzashopapi/Repository/SpecificRepositories/OrderRepository.cs diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..345b7d0 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -13,16 +13,23 @@ public DataContext() } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { + { optionsBuilder.UseNpgsql(connectionString); + } - //set primary of order? - - //seed data? + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(omi => new { omi.OrderId, omi.MenuItemId }); + base.OnModelCreating(modelBuilder); } + public DbSet Pizzas { get; set; } public DbSet Customers { get; set; } public DbSet Orders { get; set; } + public DbSet MenuItems { get; set; } + public DbSet DeliveryDrivers { get; set; } + public DbSet OrderMenuItems { get; set; } } } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..9e7f18e 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -4,30 +4,102 @@ 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()) // Use a scoped service provider { - if(!db.Customers.Any()) + var db = scope.ServiceProvider.GetRequiredService(); // Get DataContext from DI + + Console.WriteLine("🔵 Running database seeder..."); + + if (!db.Customers.Any()) + { + Console.WriteLine("🟢 Seeding Customers..."); + db.Customers.AddRange( + new Customer() { Name = "Nigel" }, + new Customer() { Name = "Dave" }, + new Customer() { Name = "Enock" } + ); + await db.SaveChangesAsync(); + } + + if (!db.Pizzas.Any()) { - db.Add(new Customer() { Name="Nigel" }); - db.Add(new Customer() { Name = "Dave" }); + Console.WriteLine("🟢 Seeding Pizzas..."); + db.Pizzas.AddRange( + new Pizza() { Name = "Cheese & Pineapple", Price = 10.99m }, + new Pizza() { Name = "Vegan Cheese Tastic", Price = 12.99m }, + new Pizza() { Name = "Pepperoni Classic", Price = 11.99m } + ); await db.SaveChangesAsync(); } - if(!db.Pizzas.Any()) + + if (!db.MenuItems.Any()) { - db.Add(new Pizza() { Name = "Cheese & Pineapple" }); - db.Add(new Pizza() { Name = "Vegan Cheese Tastic" }); + Console.WriteLine("🟢 Seeding Menu Items..."); + db.MenuItems.AddRange( + new MenuItem() { Name = "Burger", Type = "Food", Price = 8.99m }, + new MenuItem() { Name = "Fries", Type = "Food", Price = 3.99m }, + new MenuItem() { Name = "Coke", Type = "Drink", Price = 1.99m }, + new MenuItem() { Name = "Orange Juice", Type = "Drink", Price = 2.49m }, + new MenuItem() { Name = "Chocolate Milkshake", Type = "Drink", Price = 4.99m } + ); await db.SaveChangesAsync(); + } + if (!db.DeliveryDrivers.Any()) + { + Console.WriteLine("🟢 Seeding Delivery Drivers..."); + db.DeliveryDrivers.AddRange( + new DeliveryDriver() { Name = "John" }, + new DeliveryDriver() { Name = "Sarah" } + ); + await db.SaveChangesAsync(); } - //order data - if(1==1) + if (!db.Orders.Any()) { + Console.WriteLine("🟢 Seeding Orders..."); + var dave = db.Customers.First(c => c.Name == "Dave"); + var nigel = db.Customers.First(c => c.Name == "Nigel"); + var you = db.Customers.First(c => c.Name == "Enock"); + + var pineapplePizza = db.Pizzas.First(p => p.Name == "Cheese & Pineapple"); + var veganPizza = db.Pizzas.First(p => p.Name == "Vegan Cheese Tastic"); + var pepperoniPizza = db.Pizzas.First(p => p.Name == "Pepperoni Classic"); + + var driverJohn = db.DeliveryDrivers.First(d => d.Name == "John"); + var driverSarah = db.DeliveryDrivers.First(d => d.Name == "Sarah"); + + db.Orders.AddRange( + new Order() { CustomerId = dave.Id, PizzaId = pineapplePizza.Id, OrderDate = DateTime.UtcNow, DeliveryDriverId = driverJohn.Id }, + new Order() { CustomerId = nigel.Id, PizzaId = veganPizza.Id, OrderDate = DateTime.UtcNow, DeliveryDriverId = driverSarah.Id }, + new Order() { CustomerId = you.Id, PizzaId = pepperoniPizza.Id, OrderDate = DateTime.UtcNow, DeliveryDriverId = driverJohn.Id } + ); await db.SaveChangesAsync(); } + + if (!db.OrderMenuItems.Any()) + { + Console.WriteLine("Seeding Order Menu Items..."); + + var orders = db.Orders.ToList(); + var menuItems = db.MenuItems.ToList(); + + if (orders.Count > 0 && menuItems.Count > 0) + { + db.OrderMenuItems.AddRange( + new OrderMenuItem() { OrderId = orders[0].Id, MenuItemId = menuItems[0].Id, Quantity = new Random().Next(1, 5) }, // Random quantity between 1-4 + new OrderMenuItem() { OrderId = orders[1].Id, MenuItemId = menuItems[1].Id, Quantity = new Random().Next(1, 5) }, + new OrderMenuItem() { OrderId = orders[2].Id, MenuItemId = menuItems[2].Id, Quantity = new Random().Next(1, 5) } + ); + + await db.SaveChangesAsync(); + } + } + + Console.WriteLine("✅ Database seeding complete."); } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..cf88fcc 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,4 +1,8 @@ -using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Models.DTO; +using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Repository.GenericRepositories; +using exercise.pizzashopapi.Repository.SpecificRepositories; using Microsoft.AspNetCore.Mvc; namespace exercise.pizzashopapi.EndPoints @@ -7,9 +11,148 @@ public static class PizzaShopApi { public static void ConfigurePizzaShopApi(this WebApplication app) { - + var api = app.MapGroup("/api"); + + var orders = api.MapGroup("/orders"); + orders.MapGet("/", GetOrders); + orders.MapGet("/customer/{customerId}", GetOrdersByCustomer); + orders.MapPut("/{orderId}/assignDriver/{driverId}", AssignDriverToOrder); + orders.MapGet("/driver/{driverId}", GetOrdersByDriver); + + var menu = api.MapGroup("/menu"); + menu.MapPost("/addToOrder/{orderId}", AddMenuItemToOrder); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetOrders(IOrderRepository repository) + { + var orders = await repository.GetAllOrdersWithDetails(); + + var orderDTOs = orders.Select(order => new OrderDTO + { + Id = order.Id, + CustomerId = order.CustomerId, + CustomerName = order.Customer?.Name ?? "Unknown", + PizzaId = order.PizzaId, + PizzaName = order.Pizza?.Name ?? "Unknown", + PizzaPrice = order.Pizza?.Price ?? 0, + DeliveryDriverId = order.DeliveryDriverId, + DeliveryDriverName = order.DeliveryDriver?.Name ?? "Not Assigned", + OrderDate = order.OrderDate, + + // ✅ Include all menu items in the response + MenuItems = order.OrderMenuItems.Select(omi => new MenuItemDTO + { + Id = omi.MenuItemId, + Name = omi.MenuItem?.Name ?? "Unknown", + Type = omi.MenuItem?.Type ?? "Unknown", + Price = omi.MenuItem?.Price ?? 0, + Quantity = omi.Quantity + }).ToList() + + }).ToList(); + + return TypedResults.Ok(orderDTOs); } - + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static async Task GetOrdersByCustomer(IOrderRepository repository, int customerId) + { + var orders = await repository.GetOrdersByCustomer(customerId); + if (!orders.Any()) return TypedResults.NotFound($"No orders found for customer with ID {customerId}"); + + var orderDTOs = orders.Select(order => new OrderDTO + { + Id = order.Id, + CustomerId = order.CustomerId, + CustomerName = order.Customer?.Name ?? "Unknown", + PizzaId = order.PizzaId, + PizzaName = order.Pizza?.Name ?? "Unknown", + PizzaPrice = order.Pizza?.Price ?? 0, + DeliveryDriverId = order.DeliveryDriverId, + DeliveryDriverName = order.DeliveryDriver?.Name ?? "Not Assigned", + OrderDate = order.OrderDate + }).ToList(); + + return TypedResults.Ok(orderDTOs); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static async Task AssignDriverToOrder(IOrderRepository repository, int orderId, int driverId) + { + var order = await repository.GetById(orderId); + if (order == null) return TypedResults.NotFound($"Order with ID {orderId} not found"); + + order.DeliveryDriverId = driverId; + await repository.Update(order); + + // Fetch updated order with details + var updatedOrder = await repository.GetOrdersByDriver(driverId); + var orderDTOs = updatedOrder.Select(o => new OrderDTO + { + Id = o.Id, + CustomerId = o.CustomerId, + CustomerName = o.Customer?.Name ?? "Unknown", + PizzaId = o.PizzaId, + PizzaName = o.Pizza?.Name ?? "Unknown", + PizzaPrice = o.Pizza?.Price ?? 0, + DeliveryDriverId = o.DeliveryDriverId, + DeliveryDriverName = o.DeliveryDriver?.Name ?? "Not Assigned", + OrderDate = o.OrderDate + }).FirstOrDefault(); + + return TypedResults.Ok(orderDTOs); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static async Task GetOrdersByDriver(IOrderRepository repository, int driverId) + { + var orders = await repository.GetOrdersByDriver(driverId); + + if (!orders.Any()) return TypedResults.NotFound($"No orders found for driver with ID {driverId}"); + + var orderDTOs = orders.Select(order => new OrderDTO + { + Id = order.Id, + CustomerId = order.CustomerId, + CustomerName = order.Customer?.Name ?? "Unknown", + PizzaId = order.PizzaId, + PizzaName = order.Pizza?.Name ?? "Unknown", + PizzaPrice = order.Pizza?.Price ?? 0, + DeliveryDriverId = order.DeliveryDriverId, + DeliveryDriverName = order.DeliveryDriver?.Name ?? "Not Assigned", + OrderDate = order.OrderDate + }).ToList(); + + return TypedResults.Ok(orderDTOs); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static async Task AddMenuItemToOrder( + IOrderRepository orderRepository, + IOrderMenuItemRepository orderMenuItemRepository, + IRepository menuRepository, + int orderId, + [FromBody] OrderMenuItemRequest request) + { + var order = await orderRepository.GetById(orderId); + if (order == null) return TypedResults.NotFound($"Order with ID {orderId} not found"); + + var menuItem = await menuRepository.GetById(request.MenuItemId); + if (menuItem == null) return TypedResults.BadRequest($"Menu item with ID {request.MenuItemId} does not exist"); + + if (request.Quantity < 1) return TypedResults.BadRequest("Quantity must be at least 1."); + + await orderMenuItemRepository.AddMenuItemToOrder(orderId, request.MenuItemId, request.Quantity); + + return TypedResults.Ok($"Item '{menuItem.Name}' (x{request.Quantity}) added to order {orderId}"); + } + + } } diff --git a/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.Designer.cs b/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.Designer.cs new file mode 100644 index 0000000..230095d --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.Designer.cs @@ -0,0 +1,206 @@ +// +using System; +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("20250127095856_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.11") + .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") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DeliveryDrivers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.MenuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("MenuItems"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("DeliveryDriverId") + .HasColumnType("integer"); + + b.Property("OrderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PizzaId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DeliveryDriverId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderMenuItem", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("MenuItemId") + .HasColumnType("integer"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("OrderId", "MenuItemId"); + + b.HasIndex("MenuItemId"); + + b.ToTable("OrderMenuItems"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.ToTable("Pizzas"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.DeliveryDriver", "DeliveryDriver") + .WithMany() + .HasForeignKey("DeliveryDriverId"); + + b.HasOne("exercise.pizzashopapi.Models.Pizza", "Pizza") + .WithMany() + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("DeliveryDriver"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderMenuItem", b => + { + b.HasOne("exercise.pizzashopapi.Models.MenuItem", "MenuItem") + .WithMany() + .HasForeignKey("MenuItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderMenuItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MenuItem"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderMenuItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.cs b/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.cs new file mode 100644 index 0000000..057d240 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127095856_InitialMigration.cs @@ -0,0 +1,171 @@ +using System; +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: "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: "DeliveryDrivers", + 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_DeliveryDrivers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MenuItems", + 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), + Type = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MenuItems", 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: "Orders", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CustomerId = table.Column(type: "integer", nullable: false), + PizzaId = table.Column(type: "integer", nullable: false), + DeliveryDriverId = table.Column(type: "integer", nullable: true), + OrderDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Orders_DeliveryDrivers_DeliveryDriverId", + column: x => x.DeliveryDriverId, + principalTable: "DeliveryDrivers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Orders_Pizzas_PizzaId", + column: x => x.PizzaId, + principalTable: "Pizzas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderMenuItems", + columns: table => new + { + OrderId = table.Column(type: "integer", nullable: false), + MenuItemId = table.Column(type: "integer", nullable: false), + Quantity = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderMenuItems", x => new { x.OrderId, x.MenuItemId }); + table.ForeignKey( + name: "FK_OrderMenuItems_MenuItems_MenuItemId", + column: x => x.MenuItemId, + principalTable: "MenuItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderMenuItems_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrderMenuItems_MenuItemId", + table: "OrderMenuItems", + column: "MenuItemId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_CustomerId", + table: "Orders", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_DeliveryDriverId", + table: "Orders", + column: "DeliveryDriverId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_PizzaId", + table: "Orders", + column: "PizzaId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrderMenuItems"); + + migrationBuilder.DropTable( + name: "MenuItems"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "DeliveryDrivers"); + + migrationBuilder.DropTable( + name: "Pizzas"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..1b4c051 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,203 @@ +// +using System; +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", "7.0.11") + .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") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DeliveryDrivers"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.MenuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("MenuItems"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("integer"); + + b.Property("DeliveryDriverId") + .HasColumnType("integer"); + + b.Property("OrderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PizzaId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DeliveryDriverId"); + + b.HasIndex("PizzaId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderMenuItem", b => + { + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("MenuItemId") + .HasColumnType("integer"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("OrderId", "MenuItemId"); + + b.HasIndex("MenuItemId"); + + b.ToTable("OrderMenuItems"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.ToTable("Pizzas"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.DeliveryDriver", "DeliveryDriver") + .WithMany() + .HasForeignKey("DeliveryDriverId"); + + b.HasOne("exercise.pizzashopapi.Models.Pizza", "Pizza") + .WithMany() + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("DeliveryDriver"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderMenuItem", b => + { + b.HasOne("exercise.pizzashopapi.Models.MenuItem", "MenuItem") + .WithMany() + .HasForeignKey("MenuItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderMenuItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MenuItem"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderMenuItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Models/DTO/MenuItemDTO.cs b/exercise.pizzashopapi/Models/DTO/MenuItemDTO.cs new file mode 100644 index 0000000..85a69e2 --- /dev/null +++ b/exercise.pizzashopapi/Models/DTO/MenuItemDTO.cs @@ -0,0 +1,11 @@ +namespace exercise.pizzashopapi.Models.DTO +{ + public class MenuItemDTO + { + public int Id { get; set; } + public string Name { get; set; } + public string Type { get; set; } // e.g., Pizza, Burger, Drink + public decimal Price { get; set; } + public int Quantity { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/DTO/OrderDTO.cs b/exercise.pizzashopapi/Models/DTO/OrderDTO.cs new file mode 100644 index 0000000..d0a2a80 --- /dev/null +++ b/exercise.pizzashopapi/Models/DTO/OrderDTO.cs @@ -0,0 +1,17 @@ +namespace exercise.pizzashopapi.Models.DTO +{ + public class OrderDTO + { + public int Id { get; set; } + public int CustomerId { get; set; } + public string CustomerName { get; set; } + public int PizzaId { get; set; } + public string PizzaName { get; set; } + public decimal PizzaPrice { get; set; } + public int? DeliveryDriverId { get; set; } + public string DeliveryDriverName { get; set; } + public DateTime OrderDate { get; set; } + + public List MenuItems { get; set; } = new List(); + } +} diff --git a/exercise.pizzashopapi/Models/DTO/OrderMenuItemRequest.cs b/exercise.pizzashopapi/Models/DTO/OrderMenuItemRequest.cs new file mode 100644 index 0000000..f98b854 --- /dev/null +++ b/exercise.pizzashopapi/Models/DTO/OrderMenuItemRequest.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.Models.DTO +{ + public class OrderMenuItemRequest + { + public int MenuItemId { get; set; } + public int Quantity { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/DeliveryDriver.cs b/exercise.pizzashopapi/Models/DeliveryDriver.cs new file mode 100644 index 0000000..527b821 --- /dev/null +++ b/exercise.pizzashopapi/Models/DeliveryDriver.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.Models +{ + public class DeliveryDriver + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/MenuItem.cs b/exercise.pizzashopapi/Models/MenuItem.cs new file mode 100644 index 0000000..6924fd5 --- /dev/null +++ b/exercise.pizzashopapi/Models/MenuItem.cs @@ -0,0 +1,10 @@ +namespace exercise.pizzashopapi.Models +{ + public class MenuItem + { + public int Id { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } + public string Type { get; set; } // e.g., Pizza, Burger, Drink + } +} diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..f9c901c 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -4,7 +4,15 @@ namespace exercise.pizzashopapi.Models { public class Order { - - + public int Id { get; set; } + public int CustomerId { get; set; } + public int PizzaId { get; set; } + public int? DeliveryDriverId { get; set; } + public DateTime OrderDate { get; set; } + + public Customer Customer { get; set; } + public Pizza Pizza { get; set; } + public DeliveryDriver DeliveryDriver { get; set; } + public List OrderMenuItems { get; set; } = new List(); } } diff --git a/exercise.pizzashopapi/Models/OrderMenuItem.cs b/exercise.pizzashopapi/Models/OrderMenuItem.cs new file mode 100644 index 0000000..3ba8b89 --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderMenuItem.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace exercise.pizzashopapi.Models +{ + public class OrderMenuItem + { + [Key] // Primary key part 1 + public int OrderId { get; set; } + public Order Order { get; set; } + + [Key] // Primary key part 2 + public int MenuItemId { get; set; } + public MenuItem MenuItem { get; set; } + + public int Quantity { get; set; } // Allows multiple of the same item + } +} diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index c04a440..a704874 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -1,13 +1,20 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.EndPoints; using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Repository.GenericRepositories; +using exercise.pizzashopapi.Repository.SpecificRepositories; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); -builder.Services.AddScoped(); +builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + builder.Services.AddDbContext(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -15,6 +22,8 @@ var app = builder.Build(); +await app.SeedPizzaShopApi(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -30,6 +39,4 @@ app.ConfigurePizzaShopApi(); -app.SeedPizzaShopApi(); - app.Run(); diff --git a/exercise.pizzashopapi/Repository/GenericRepositories/IRepository.cs b/exercise.pizzashopapi/Repository/GenericRepositories/IRepository.cs new file mode 100644 index 0000000..ed9a67d --- /dev/null +++ b/exercise.pizzashopapi/Repository/GenericRepositories/IRepository.cs @@ -0,0 +1,13 @@ +using exercise.pizzashopapi.Models; + +namespace exercise.pizzashopapi.Repository.GenericRepositories +{ + public interface IRepository where T : class + { + Task> GetAll(); + Task GetById(int id); + Task Add(T entity); + Task Update(T entity); + Task Delete(int id); + } +} diff --git a/exercise.pizzashopapi/Repository/GenericRepositories/Repository.cs b/exercise.pizzashopapi/Repository/GenericRepositories/Repository.cs new file mode 100644 index 0000000..5f9a44a --- /dev/null +++ b/exercise.pizzashopapi/Repository/GenericRepositories/Repository.cs @@ -0,0 +1,51 @@ +using System.Linq.Expressions; +using exercise.pizzashopapi.Data; +using exercise.pizzashopapi.Models; +using Microsoft.EntityFrameworkCore; + +namespace exercise.pizzashopapi.Repository.GenericRepositories +{ + public class Repository : IRepository where T : class + { + private readonly DataContext _dbContext; + private readonly DbSet _dbSet; + + public Repository(DataContext dbContext) + { + _dbContext = dbContext; + _dbSet = _dbContext.Set(); + } + + public async Task> GetAll() + { + return await _dbSet.ToListAsync(); + + } + + public async Task GetById(int id) + { + return await _dbSet.FindAsync(id); + } + public async Task Add(T entity) + { + await _dbSet.AddAsync(entity); + await _dbContext.SaveChangesAsync(); + } + + public async Task Update(T entity) + { + _dbSet.Update(entity); + await _dbContext.SaveChangesAsync(); + } + + public async Task Delete(int id) + { + var entity = await GetById(id); + if (entity != null) + { + _dbSet.Remove(entity); + await _dbContext.SaveChangesAsync(); + } + } + } +} diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs deleted file mode 100644 index b114ea8..0000000 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using exercise.pizzashopapi.Models; - -namespace exercise.pizzashopapi.Repository -{ - public interface IRepository - { - Task> GetOrdersByCustomer(int id); - - - } -} diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs deleted file mode 100644 index e109fce..0000000 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using exercise.pizzashopapi.Data; -using exercise.pizzashopapi.Models; - -namespace exercise.pizzashopapi.Repository -{ - public class Repository : IRepository - { - private DataContext _db; - public Task> GetOrdersByCustomer(int id) - { - throw new NotImplementedException(); - } - } -} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/IMenuItemRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/IMenuItemRepository.cs new file mode 100644 index 0000000..20d245a --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/IMenuItemRepository.cs @@ -0,0 +1,10 @@ +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public interface IMenuItemRepository : IRepository + { + Task> GetMenuItemsByType(string type); + } +} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderMenuItemRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderMenuItemRepository.cs new file mode 100644 index 0000000..9a799c3 --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderMenuItemRepository.cs @@ -0,0 +1,11 @@ +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public interface IOrderMenuItemRepository : IRepository + { + Task AddMenuItemToOrder(int orderId, int menuItemId, int quantity); + Task> GetMenuItemsByOrder(int orderId); + } +} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderRepository.cs new file mode 100644 index 0000000..149beba --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/IOrderRepository.cs @@ -0,0 +1,12 @@ +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public interface IOrderRepository : IRepository + { + Task> GetOrdersByCustomer(int customerId); + Task> GetOrdersByDriver(int driverId); + Task> GetAllOrdersWithDetails(); + } +} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/MenuItemRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/MenuItemRepository.cs new file mode 100644 index 0000000..7bfe59c --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/MenuItemRepository.cs @@ -0,0 +1,24 @@ +using exercise.pizzashopapi.Data; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; +using Microsoft.EntityFrameworkCore; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public class MenuItemRepository : Repository, IMenuItemRepository + { + private readonly DataContext _dbContext; + + public MenuItemRepository(DataContext dbContext) : base(dbContext) + { + _dbContext = dbContext; + } + + public async Task> GetMenuItemsByType(string type) + { + return await _dbContext.MenuItems + .Where(m => m.Type == type) + .ToListAsync(); + } + } +} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/OrderMenuItemRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/OrderMenuItemRepository.cs new file mode 100644 index 0000000..6c148f6 --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/OrderMenuItemRepository.cs @@ -0,0 +1,49 @@ +using exercise.pizzashopapi.Data; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; +using Microsoft.EntityFrameworkCore; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public class OrderMenuItemRepository : Repository, IOrderMenuItemRepository + { + private readonly DataContext _context; + + public OrderMenuItemRepository(DataContext context) : base(context) + { + _context = context; + } + + public async Task AddMenuItemToOrder(int orderId, int menuItemId, int quantity) + { + var existingEntry = await _context.OrderMenuItems + .FirstOrDefaultAsync(omi => omi.OrderId == orderId && omi.MenuItemId == menuItemId); + + if (existingEntry != null) + { + existingEntry.Quantity += quantity; // Increase quantity if the item already exists + } + else + { + var orderMenuItem = new OrderMenuItem + { + OrderId = orderId, + MenuItemId = menuItemId, + Quantity = quantity + }; + _context.OrderMenuItems.Add(orderMenuItem); + } + + await _context.SaveChangesAsync(); + } + + + public async Task> GetMenuItemsByOrder(int orderId) + { + return await _context.OrderMenuItems + .Include(omi => omi.MenuItem) + .Where(omi => omi.OrderId == orderId) + .ToListAsync(); + } + } +} diff --git a/exercise.pizzashopapi/Repository/SpecificRepositories/OrderRepository.cs b/exercise.pizzashopapi/Repository/SpecificRepositories/OrderRepository.cs new file mode 100644 index 0000000..ce1773e --- /dev/null +++ b/exercise.pizzashopapi/Repository/SpecificRepositories/OrderRepository.cs @@ -0,0 +1,49 @@ +using exercise.pizzashopapi.Data; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository.GenericRepositories; +using Microsoft.EntityFrameworkCore; + +namespace exercise.pizzashopapi.Repository.SpecificRepositories +{ + public class OrderRepository : Repository, IOrderRepository + { + private readonly DataContext _dbContext; + + public OrderRepository(DataContext dbContext) : base(dbContext) + { + _dbContext = dbContext; + } + + public async Task> GetOrdersByCustomer(int customerId) + { + return await _dbContext.Orders + .Include(o => o.Customer) + .Include(o => o.Pizza) + .Include(o => o.DeliveryDriver) + .Where(o => o.CustomerId == customerId) + .ToListAsync(); + } + + public async Task> GetOrdersByDriver(int driverId) + { + return await _dbContext.Orders + .Include(o => o.Customer) + .Include(o => o.Pizza) + .Include(o => o.DeliveryDriver) + .Where(o => o.DeliveryDriverId == driverId) + .ToListAsync(); + } + + public async Task> GetAllOrdersWithDetails() + { + return await _dbContext.Orders + .Include(o => o.Customer) + .Include(o => o.Pizza) + .Include(o => o.DeliveryDriver) + .Include(o => o.OrderMenuItems) + .ThenInclude(omi => omi.MenuItem) + .ToListAsync(); + } + + } +}