From e7ff6ef6f09f4009a27e01c08d4e81fe059262af Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Mon, 27 Jan 2025 09:40:32 +0100 Subject: [PATCH 1/5] finished core --- exercise.pizzashopapi/DTO/CreateOrderDto.cs | 8 ++ exercise.pizzashopapi/DTO/CustomerDto.cs | 7 + exercise.pizzashopapi/DTO/OrderDto.cs | 11 ++ exercise.pizzashopapi/DTO/PizzaDto.cs | 9 ++ exercise.pizzashopapi/Data/DataContext.cs | 14 +- exercise.pizzashopapi/Data/Seeder.cs | 15 +- .../EndPoints/PizzaShopApi.cs | 113 ++++++++++++++- .../20250127082959_first.Designer.cs | 129 ++++++++++++++++++ .../Migrations/20250127082959_first.cs | 95 +++++++++++++ .../Migrations/DataContextModelSnapshot.cs | 126 +++++++++++++++++ exercise.pizzashopapi/Models/Customer.cs | 3 + exercise.pizzashopapi/Models/Order.cs | 18 ++- exercise.pizzashopapi/Models/Pizza.cs | 7 +- exercise.pizzashopapi/Program.cs | 5 +- .../Repository/IRepository.cs | 20 ++- .../Repository/Repository.cs | 98 ++++++++++++- 16 files changed, 652 insertions(+), 26 deletions(-) create mode 100644 exercise.pizzashopapi/DTO/CreateOrderDto.cs create mode 100644 exercise.pizzashopapi/DTO/CustomerDto.cs create mode 100644 exercise.pizzashopapi/DTO/OrderDto.cs create mode 100644 exercise.pizzashopapi/DTO/PizzaDto.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127082959_first.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127082959_first.cs create mode 100644 exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs diff --git a/exercise.pizzashopapi/DTO/CreateOrderDto.cs b/exercise.pizzashopapi/DTO/CreateOrderDto.cs new file mode 100644 index 0000000..d9b391c --- /dev/null +++ b/exercise.pizzashopapi/DTO/CreateOrderDto.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO +{ + public class CreateOrderDto + { + public int CustomerId { get; set; } + public int PizzaId { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/CustomerDto.cs b/exercise.pizzashopapi/DTO/CustomerDto.cs new file mode 100644 index 0000000..30c7cdc --- /dev/null +++ b/exercise.pizzashopapi/DTO/CustomerDto.cs @@ -0,0 +1,7 @@ +namespace exercise.pizzashopapi.DTO +{ + public class CustomerDto + { + 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..2a4e826 --- /dev/null +++ b/exercise.pizzashopapi/DTO/OrderDto.cs @@ -0,0 +1,11 @@ +namespace exercise.pizzashopapi.DTO +{ + public class OrderDto + { + public string Customer { get; set; } + public string Pizza { get; set; } + public decimal Price { get; set; } + public DateTime OrderedAt { get; set; } + public bool IsDelivered { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/PizzaDto.cs b/exercise.pizzashopapi/DTO/PizzaDto.cs new file mode 100644 index 0000000..0ef21a5 --- /dev/null +++ b/exercise.pizzashopapi/DTO/PizzaDto.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class PizzaDto + { + public string Name { get; set; } + public decimal Price { get; set; } + + } +} diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..2907ac7 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -12,14 +12,18 @@ public DataContext() connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString"); } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + //Seeder seeder = new Seeder(); + modelBuilder.Entity().HasKey(p => p.Id); + modelBuilder.Entity().HasKey(c => c.Id); + + modelBuilder.Entity().HasKey(o => new { o.PizzaId, o.CustomerId }); + modelBuilder.Entity().HasKey(o => o.Id); + } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseNpgsql(connectionString); - - //set primary of order? - - //seed data? - } public DbSet Pizzas { get; set; } public DbSet Customers { get; set; } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..510354b 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -10,24 +10,29 @@ public async static void SeedPizzaShopApi(this WebApplication app) { if(!db.Customers.Any()) { - db.Add(new Customer() { Name="Nigel" }); db.Add(new Customer() { Name = "Dave" }); + db.Add(new Customer() { Name = "Nigel" }); + db.Add(new Customer() { Name = "Andreas" }); await db.SaveChangesAsync(); } 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 = 10}); + db.Add(new Pizza() { Name = "Vegan Cheese Tastic", Price = 15 }); + db.Add(new Pizza() { Name = "Mushrooms & Onion" , Price = 8}); await db.SaveChangesAsync(); } //order data - if(1==1) + if (!db.Orders.Any()) { - + db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderedAt = new DateTime(2025, 1, 20, 8,30,0, DateTimeKind.Utc),Price = 10 }); + db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderedAt = new DateTime(2025, 1, 23, 19, 10, 0, DateTimeKind.Utc), Price = 15 }); + db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderedAt = new DateTime(2025, 1, 26, 16,45,0, DateTimeKind.Utc), Price = 8 }); await db.SaveChangesAsync(); } + } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..0d1d896 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,4 +1,6 @@ -using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.DTO; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository; using Microsoft.AspNetCore.Mvc; namespace exercise.pizzashopapi.EndPoints @@ -7,9 +9,114 @@ public static class PizzaShopApi { public static void ConfigurePizzaShopApi(this WebApplication app) { - + var shop = app.MapGroup("/shop"); + + shop.MapGet("/pizzas", GetPizzas); + shop.MapGet("/customers", GetCustomers); + shop.MapGet("/orders", GetOrders); + shop.MapGet("/orders/{customerId}", GetOrdersByCustomerId); + shop.MapPost("/orders", CreateOrder); + shop.MapPost("/customers", CreateCustomer); + shop.MapPost("/pizzas", CreatePizza); + shop.MapGet("/pizzas/{id}", GetPizzaById); + } + + private static async Task GetPizzas(IRepository repository) + { + var pizzas = await repository.Get(); + return TypedResults.Ok(pizzas.Select(p => + new PizzaDto() { + Name = p.Name, + Price = p.Price + })); + } + + private static async Task GetCustomers(IRepository repository) + { + var customers = await repository.Get(); + return TypedResults.Ok(customers.Select(c => + new CustomerDto() { + Name = c.Name + })); } - + private static async Task GetOrders(IRepository repository) + { + var orders = await repository.GetWithIncludes(o => o.Customer, o => o.Pizza); + return TypedResults.Ok(orders.Select(o => new OrderDto() + { + Customer = o.Customer.Name, + Pizza = o.Pizza.Name, + OrderedAt = o.OrderedAt, + Price = o.Price, + IsDelivered = o.IsDelivered + })); + } + + private static async Task GetOrdersByCustomerId(IRepository repository, int customerId) + { + var orders = await repository.GetWithIncludes(o => o.Customer, o => o.Pizza); + return TypedResults.Ok(orders.Where(o => o.CustomerId == customerId).Select(o => new OrderDto() + { + Customer = o.Customer.Name, + Pizza = o.Pizza.Name, + OrderedAt = o.OrderedAt, + Price = o.Price, + IsDelivered = o.IsDelivered + })); + } + + private static async Task CreateOrder(IRepository repository, IRepository pizzaRepository, CreateOrderDto orderDto) + { + + PizzaDto pizzaDto = (PizzaDto)await GetPizzaById(pizzaRepository, orderDto.PizzaId); + var order = new Order() + { + CustomerId = orderDto.CustomerId, + PizzaId = orderDto.PizzaId, + OrderedAt = DateTime.Now, + Price = pizzaDto.Price, + IsDelivered = false + }; + await repository.Insert(order); + return TypedResults.Created(); + } + + private static async Task CreateCustomer(IRepository repository, string customerName) + { + var customer = new Customer() + { + Name = customerName + }; + await repository.Insert(customer); + return TypedResults.Created(); + } + + private static async Task CreatePizza(IRepository repository, PizzaDto pizzaDto) + { + var pizza = new Pizza() + { + Name = pizzaDto.Name, + Price = pizzaDto.Price + }; + await repository.Insert(pizza); + return TypedResults.Created(); + } + + private static async Task GetPizzaById(IRepository repository, int id) + { + var pizza = await repository.GetById(id); + if (pizza == null) + { + return TypedResults.NotFound(); + } + return TypedResults.Ok(new PizzaDto() + { + Name = pizza.Name, + Price = pizza.Price + }); + } + + } } diff --git a/exercise.pizzashopapi/Migrations/20250127082959_first.Designer.cs b/exercise.pizzashopapi/Migrations/20250127082959_first.Designer.cs new file mode 100644 index 0000000..9712e9f --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127082959_first.Designer.cs @@ -0,0 +1,129 @@ +// +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("20250127082959_first")] + partial class first + { + /// + 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") + .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("IsDelivered") + .HasColumnType("boolean") + .HasColumnName("is_delivered"); + + b.Property("OrderedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ordered_at"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("orders"); + }); + + 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.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany() + .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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127082959_first.cs b/exercise.pizzashopapi/Migrations/20250127082959_first.cs new file mode 100644 index 0000000..9cb05b9 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127082959_first.cs @@ -0,0 +1,95 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class first : 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: "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), + ordered_at = table.Column(type: "timestamp with time zone", nullable: false), + is_delivered = table.Column(type: "boolean", nullable: false), + price = table.Column(type: "numeric", 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.CreateIndex( + name: "IX_orders_customer_id", + table: "orders", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "IX_orders_pizza_id", + table: "orders", + column: "pizza_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "orders"); + + 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..a5dfe69 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,126 @@ +// +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") + .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("IsDelivered") + .HasColumnType("boolean") + .HasColumnName("is_delivered"); + + b.Property("OrderedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ordered_at"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("orders"); + }); + + 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.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany() + .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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Models/Customer.cs b/exercise.pizzashopapi/Models/Customer.cs index 2ca83bd..12e06e2 100644 --- a/exercise.pizzashopapi/Models/Customer.cs +++ b/exercise.pizzashopapi/Models/Customer.cs @@ -2,9 +2,12 @@ namespace exercise.pizzashopapi.Models { + [Table("customers")] public class Customer { + [Column("id")] public int Id { get; set; } + [Column("name")] public string Name { get; set; } } } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..7ccc6ad 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -2,9 +2,23 @@ namespace exercise.pizzashopapi.Models { + [Table("orders")] public class Order { - - + [Column("id")] + public int Id { get; set; } + [Column("customer_id")] + public int CustomerId { get; set; } + public Customer Customer { get; set; } + [Column("pizza_id")] + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + [Column("ordered_at")] + public DateTime OrderedAt { get; set; } = DateTime.Now; + [Column("is_delivered")] + public bool IsDelivered { get; set; } = false; + [Column("price")] + public decimal Price { get; set; } + } } diff --git a/exercise.pizzashopapi/Models/Pizza.cs b/exercise.pizzashopapi/Models/Pizza.cs index 5c085ec..99108b1 100644 --- a/exercise.pizzashopapi/Models/Pizza.cs +++ b/exercise.pizzashopapi/Models/Pizza.cs @@ -2,11 +2,14 @@ namespace exercise.pizzashopapi.Models { - + [Table("pizzas")] public class Pizza - { + { + [Column("id")] public int Id { get; set; } + [Column("name")] public string Name { get; set; } + [Column("price")] public decimal Price { get; set; } } } \ No newline at end of file diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index c04a440..8547623 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -1,5 +1,6 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.EndPoints; +using exercise.pizzashopapi.Models; using exercise.pizzashopapi.Repository; var builder = WebApplication.CreateBuilder(args); @@ -7,7 +8,9 @@ // 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.AddDbContext(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index b114ea8..7e97163 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -1,11 +1,23 @@ -using exercise.pizzashopapi.Models; +using System.Linq.Expressions; +using exercise.pizzashopapi.Models; namespace exercise.pizzashopapi.Repository { - public interface IRepository + public interface IRepository { - Task> GetOrdersByCustomer(int id); - + Task> Get(); + Task Insert(T entity); + Task Update(T entity); + Task Delete(object id); + Task Save(); + Task GetById(int id); + Task> GetWithIncludes(params Expression>[] includes); + Task> GetWithThenIncludes(params Func, IQueryable>[] includes); + Task GetByIdWithIncludes(int id, params Expression>[] includes); + Task GetByIdWithThenIncludes(int id, params Func, IQueryable>[] includes); + Task> InsertAll(IEnumerable entities); + + } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index e109fce..f318901 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -1,14 +1,104 @@ -using exercise.pizzashopapi.Data; +using System.Linq.Expressions; +using exercise.pizzashopapi.Data; using exercise.pizzashopapi.Models; +using Microsoft.EntityFrameworkCore; 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 Insert(T entity) + { + _table.Add(entity); + _db.SaveChanges(); + return entity; + } + public async Task> InsertAll(IEnumerable entities) + { + _table.AddRange(entities); + _db.SaveChanges(); + return entities; + } + + 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 _table.Find(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 GetByIdWithIncludes(int id, params Expression>[] includes) + { + IQueryable query = _table.Where(e => EF.Property(e, "Id") == id); + foreach (var include in includes) + { + query = query.Include(include); + } + return await query.FirstOrDefaultAsync(); + } + + public async Task> GetWithThenIncludes( + params Func, IQueryable>[] includes) + { + IQueryable query = _table; + foreach (var include in includes) + { + query = include(query); + } + return await query.ToListAsync(); + } + + public async Task GetByIdWithThenIncludes(int id, + params Func, IQueryable>[] includes) { - throw new NotImplementedException(); + IQueryable query = _table.Where(e => EF.Property(e, "Id") == id); + foreach (var include in includes) + { + query = include(query); + } + return await query.FirstOrDefaultAsync(); } + + public async Task Save() + { + _db.SaveChangesAsync(); + } + } } From e012da85f9b676b1d72515695d29338acb065e04 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Mon, 27 Jan 2025 10:38:34 +0100 Subject: [PATCH 2/5] extension 1 --- exercise.pizzashopapi/DTO/CreateOrderDto.cs | 1 + exercise.pizzashopapi/DTO/CreatedOrderDto.cs | 13 + exercise.pizzashopapi/DTO/ToppingDto.cs | 8 + exercise.pizzashopapi/DTO/UpdateOrderDto.cs | 9 + exercise.pizzashopapi/Data/DataContext.cs | 6 + exercise.pizzashopapi/Data/Seeder.cs | 32 ++- .../EndPoints/PizzaShopApi.cs | 91 ++++++- .../20250127091731_toppings.Designer.cs | 237 ++++++++++++++++++ .../Migrations/20250127091731_toppings.cs | 107 ++++++++ .../Migrations/DataContextModelSnapshot.cs | 108 ++++++++ exercise.pizzashopapi/Models/Order.cs | 6 +- exercise.pizzashopapi/Models/OrderToppings.cs | 17 ++ exercise.pizzashopapi/Models/Topping.cs | 18 ++ exercise.pizzashopapi/Program.cs | 2 + 14 files changed, 639 insertions(+), 16 deletions(-) create mode 100644 exercise.pizzashopapi/DTO/CreatedOrderDto.cs create mode 100644 exercise.pizzashopapi/DTO/ToppingDto.cs create mode 100644 exercise.pizzashopapi/DTO/UpdateOrderDto.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127091731_toppings.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127091731_toppings.cs create mode 100644 exercise.pizzashopapi/Models/OrderToppings.cs create mode 100644 exercise.pizzashopapi/Models/Topping.cs diff --git a/exercise.pizzashopapi/DTO/CreateOrderDto.cs b/exercise.pizzashopapi/DTO/CreateOrderDto.cs index d9b391c..b7a2c68 100644 --- a/exercise.pizzashopapi/DTO/CreateOrderDto.cs +++ b/exercise.pizzashopapi/DTO/CreateOrderDto.cs @@ -4,5 +4,6 @@ public class CreateOrderDto { public int CustomerId { get; set; } public int PizzaId { get; set; } + public List ToppingIds { get; set; } } } diff --git a/exercise.pizzashopapi/DTO/CreatedOrderDto.cs b/exercise.pizzashopapi/DTO/CreatedOrderDto.cs new file mode 100644 index 0000000..4db68b5 --- /dev/null +++ b/exercise.pizzashopapi/DTO/CreatedOrderDto.cs @@ -0,0 +1,13 @@ +namespace exercise.pizzashopapi.DTO +{ + public class CreatedOrderDto + { + public string Customer { get; set; } + public string Pizza { get; set; } + public IEnumerable Toppings { get; set; } + public decimal Price { get; set; } + public bool IsDelivered { get; set; } + public DateTime OrderedAt { get; set; } + + } } + diff --git a/exercise.pizzashopapi/DTO/ToppingDto.cs b/exercise.pizzashopapi/DTO/ToppingDto.cs new file mode 100644 index 0000000..1278828 --- /dev/null +++ b/exercise.pizzashopapi/DTO/ToppingDto.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO +{ + public class ToppingDto + { + public string Name { get; set; } + public decimal Price { get; set; } + } +} diff --git a/exercise.pizzashopapi/DTO/UpdateOrderDto.cs b/exercise.pizzashopapi/DTO/UpdateOrderDto.cs new file mode 100644 index 0000000..d7ca1b0 --- /dev/null +++ b/exercise.pizzashopapi/DTO/UpdateOrderDto.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.DTO +{ + public class UpdateOrderDto + { + public bool IsDelivered { get; set; } + public decimal Price { get; set; } + + } +} diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 2907ac7..176bab5 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -20,6 +20,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasKey(o => new { o.PizzaId, o.CustomerId }); modelBuilder.Entity().HasKey(o => o.Id); + modelBuilder.Entity().HasKey(t => t.Id); + modelBuilder.Entity().HasKey(ot => new { ot.OrderId, ot.ToppingId }); + modelBuilder.Entity().HasKey(o => o.Id); + } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -28,5 +32,7 @@ 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 510354b..188dce8 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -6,20 +6,20 @@ public static class Seeder { public async static void SeedPizzaShopApi(this WebApplication app) { - using(var db = new DataContext()) + using (var db = new DataContext()) { - if(!db.Customers.Any()) + if (!db.Customers.Any()) { db.Add(new Customer() { Name = "Dave" }); db.Add(new Customer() { Name = "Nigel" }); db.Add(new Customer() { Name = "Andreas" }); await db.SaveChangesAsync(); } - if(!db.Pizzas.Any()) + if (!db.Pizzas.Any()) { - db.Add(new Pizza() { Name = "Cheese & Pineapple" , Price = 10}); + db.Add(new Pizza() { Name = "Cheese & Pineapple", Price = 10 }); db.Add(new Pizza() { Name = "Vegan Cheese Tastic", Price = 15 }); - db.Add(new Pizza() { Name = "Mushrooms & Onion" , Price = 8}); + db.Add(new Pizza() { Name = "Mushrooms & Onion", Price = 8 }); await db.SaveChangesAsync(); } @@ -27,12 +27,30 @@ public async static void SeedPizzaShopApi(this WebApplication app) //order data if (!db.Orders.Any()) { - db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderedAt = new DateTime(2025, 1, 20, 8,30,0, DateTimeKind.Utc),Price = 10 }); + db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderedAt = new DateTime(2025, 1, 20, 8, 30, 0, DateTimeKind.Utc), Price = 10 }); db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderedAt = new DateTime(2025, 1, 23, 19, 10, 0, DateTimeKind.Utc), Price = 15 }); - db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderedAt = new DateTime(2025, 1, 26, 16,45,0, DateTimeKind.Utc), Price = 8 }); + db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderedAt = new DateTime(2025, 1, 26, 16, 45, 0, DateTimeKind.Utc), Price = 8 }); await db.SaveChangesAsync(); } + if (!db.Toppings.Any()) + { + db.Add(new Topping() { Name = "Bacon" , Price = 3}); + db.Add(new Topping() { Name = "Pineapple", Price = 2 }); + db.Add(new Topping() { Name = "Vegan Cheese", Price = 1 }); + db.Add(new Topping() { Name = "Mushrooms", Price = 1 }); + db.Add(new Topping() { Name = "Onion", Price = 1 }); + await db.SaveChangesAsync(); + } + + if (!db.OrderToppings.Any()) + { + db.Add(new OrderToppings() { OrderId = 1, ToppingId = 2 }); + db.Add(new OrderToppings() { OrderId = 2, ToppingId = 3 }); + db.Add(new OrderToppings() { OrderId = 3, ToppingId = 4 }); + db.Add(new OrderToppings() { OrderId = 3, ToppingId = 5 }); + await db.SaveChangesAsync(); + } } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index 0d1d896..2d6c75e 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -19,6 +19,8 @@ public static void ConfigurePizzaShopApi(this WebApplication app) shop.MapPost("/customers", CreateCustomer); shop.MapPost("/pizzas", CreatePizza); shop.MapGet("/pizzas/{id}", GetPizzaById); + shop.MapGet("/toppings/{id}", GetTopping); + shop.MapGet("/toppings", GetToppings); } private static async Task GetPizzas(IRepository repository) @@ -66,20 +68,71 @@ private static async Task GetOrdersByCustomerId(IRepository repo })); } - private static async Task CreateOrder(IRepository repository, IRepository pizzaRepository, CreateOrderDto orderDto) + private static async Task CreateOrder(IRepository repository, IRepository pizzaRepository, IRepository toppingRepository, CreateOrderDto orderDto) { - - PizzaDto pizzaDto = (PizzaDto)await GetPizzaById(pizzaRepository, orderDto.PizzaId); + var pizza = await pizzaRepository.GetById(orderDto.PizzaId); + if (pizza == null) + { + return TypedResults.NotFound(); + } var order = new Order() { CustomerId = orderDto.CustomerId, PizzaId = orderDto.PizzaId, - OrderedAt = DateTime.Now, - Price = pizzaDto.Price, - IsDelivered = false + OrderedAt = DateTime.UtcNow, + Price = pizza.Price }; - await repository.Insert(order); - return TypedResults.Created(); + orderDto.ToppingIds.ForEach(async toppingId => + { + Topping thisTopping = await toppingRepository.GetById(toppingId); + var orderTopping = new OrderToppings() + { + ToppingId = toppingId, + }; + order.OrderToppings.Add(orderTopping); + order.Price += thisTopping.Price; + order.Toppings.Add(thisTopping); + }); + var orderedPizza = await repository.Insert(order); + var pizzaWithIncludes = await repository.GetByIdWithIncludes(orderedPizza.Id, o => o.Customer, o => o.Pizza, o => o.Toppings); + CreatedOrderDto createdOrder = new CreatedOrderDto() + { + Customer = pizzaWithIncludes.Customer.Name, + Pizza = pizzaWithIncludes.Pizza.Name, + OrderedAt = pizzaWithIncludes.OrderedAt, + Price = pizzaWithIncludes.Price, + IsDelivered = pizzaWithIncludes.IsDelivered, + Toppings = pizzaWithIncludes.Toppings.Select(t => new ToppingDto() + { + Name = t.Name, + Price = t.Price + }) + }; + return TypedResults.Created("apath", createdOrder); + + } + + private static async Task UpdateOrder(IRepository repository, int orderId, UpdateOrderDto orderDto) + { + var order = await repository.GetById(orderId); + if (order == null) + { + return TypedResults.NotFound(); + } + if(orderDto.IsDelivered != null) + order.IsDelivered = orderDto.IsDelivered; + if(orderDto.Price != null) + order.Price = orderDto.Price; + var updatedOrder = await repository.Update(order); + OrderDto responseDto = new OrderDto() + { + Customer = updatedOrder.Customer.Name, + Pizza = updatedOrder.Pizza.Name, + OrderedAt = updatedOrder.OrderedAt, + Price = updatedOrder.Price, + IsDelivered = updatedOrder.IsDelivered + }; + return TypedResults.Ok(responseDto); } private static async Task CreateCustomer(IRepository repository, string customerName) @@ -117,6 +170,28 @@ private static async Task GetPizzaById(IRepository repository, i }); } + private static async Task GetTopping(IRepository repository, int id) + { + var topping = await repository.GetById(id); + if (topping == null) + { + return TypedResults.NotFound(); + } + return TypedResults.Ok(new ToppingDto() + { + Name = topping.Name, + Price = topping.Price + }); + } + private static async Task GetToppings(IRepository repository) + { + var toppings = await repository.Get(); + return TypedResults.Ok(toppings.Select(t => new ToppingDto() + { + Name = t.Name, + Price = t.Price + })); + } } } diff --git a/exercise.pizzashopapi/Migrations/20250127091731_toppings.Designer.cs b/exercise.pizzashopapi/Migrations/20250127091731_toppings.Designer.cs new file mode 100644 index 0000000..d936338 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127091731_toppings.Designer.cs @@ -0,0 +1,237 @@ +// +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("20250127091731_toppings")] + partial class toppings + { + /// + 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("OrderTopping", b => + { + b.Property("OrdersId") + .HasColumnType("integer"); + + b.Property("ToppingsId") + .HasColumnType("integer"); + + b.HasKey("OrdersId", "ToppingsId"); + + b.HasIndex("ToppingsId"); + + b.ToTable("OrderTopping"); + }); + + 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("IsDelivered") + .HasColumnType("boolean") + .HasColumnName("is_delivered"); + + b.Property("OrderedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ordered_at"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PizzaId"); + + b.ToTable("orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + 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("order_toppings"); + }); + + 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.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("OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", null) + .WithMany() + .HasForeignKey("OrdersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + 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.Pizza", "Pizza") + .WithMany() + .HasForeignKey("PizzaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + + b.Navigation("Pizza"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany("OrderToppings") + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Navigation("OrderToppings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127091731_toppings.cs b/exercise.pizzashopapi/Migrations/20250127091731_toppings.cs new file mode 100644 index 0000000..450cdda --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127091731_toppings.cs @@ -0,0 +1,107 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class toppings : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + 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: "order_toppings", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + order_id = table.Column(type: "integer", nullable: false), + topping_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_order_toppings", x => x.id); + table.ForeignKey( + name: "FK_order_toppings_orders_order_id", + column: x => x.order_id, + principalTable: "orders", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_order_toppings_toppings_topping_id", + column: x => x.topping_id, + principalTable: "toppings", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderTopping", + columns: table => new + { + OrdersId = table.Column(type: "integer", nullable: false), + ToppingsId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderTopping", x => new { x.OrdersId, x.ToppingsId }); + table.ForeignKey( + name: "FK_OrderTopping_orders_OrdersId", + column: x => x.OrdersId, + principalTable: "orders", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderTopping_toppings_ToppingsId", + column: x => x.ToppingsId, + principalTable: "toppings", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_order_toppings_order_id", + table: "order_toppings", + column: "order_id"); + + migrationBuilder.CreateIndex( + name: "IX_order_toppings_topping_id", + table: "order_toppings", + column: "topping_id"); + + migrationBuilder.CreateIndex( + name: "IX_OrderTopping_ToppingsId", + table: "OrderTopping", + column: "ToppingsId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "order_toppings"); + + migrationBuilder.DropTable( + name: "OrderTopping"); + + migrationBuilder.DropTable( + name: "toppings"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs index a5dfe69..d8cf8c9 100644 --- a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -22,6 +22,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("OrderTopping", b => + { + b.Property("OrdersId") + .HasColumnType("integer"); + + b.Property("ToppingsId") + .HasColumnType("integer"); + + b.HasKey("OrdersId", "ToppingsId"); + + b.HasIndex("ToppingsId"); + + b.ToTable("OrderTopping"); + }); + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => { b.Property("Id") @@ -79,6 +94,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("orders"); }); + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + 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("order_toppings"); + }); + modelBuilder.Entity("exercise.pizzashopapi.Models.Pizza", b => { b.Property("Id") @@ -102,6 +143,44 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("pizzas"); }); + 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("OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", null) + .WithMany() + .HasForeignKey("OrdersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => { b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") @@ -120,6 +199,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Pizza"); }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany("OrderToppings") + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Navigation("OrderToppings"); + }); #pragma warning restore 612, 618 } } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index 7ccc6ad..fe25ddb 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -14,11 +14,15 @@ public class Order public int PizzaId { get; set; } public Pizza Pizza { get; set; } [Column("ordered_at")] - public DateTime OrderedAt { get; set; } = DateTime.Now; + public DateTime OrderedAt { get; set; } = DateTime.UtcNow; [Column("is_delivered")] public bool IsDelivered { get; set; } = false; [Column("price")] public decimal Price { get; set; } + public List Toppings { get; set; } = new List(); + + public List OrderToppings { get; set; } = new List(); + } } diff --git a/exercise.pizzashopapi/Models/OrderToppings.cs b/exercise.pizzashopapi/Models/OrderToppings.cs new file mode 100644 index 0000000..133af67 --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderToppings.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.Models +{ + [Table("order_toppings")] + public class OrderToppings + { + [Column("id")] + public int Id { get; set; } + [Column("order_id")] + public int OrderId { get; set; } + public Order Order { get; set; } + [Column("topping_id")] + public int ToppingId { get; set; } + public Topping Topping { get; set; } + } +} diff --git a/exercise.pizzashopapi/Models/Topping.cs b/exercise.pizzashopapi/Models/Topping.cs new file mode 100644 index 0000000..90c7836 --- /dev/null +++ b/exercise.pizzashopapi/Models/Topping.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.Models +{ + [Table("toppings")] + public class Topping + { + [Column("id")] + public int Id { get; set; } + [Column("name")] + public string Name { get; set; } + [Column("price")] + public decimal Price { get; set; } + + public List OrderToppings { get; set; } = new List(); + public List Orders { get; set; } = new List(); + } +} diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index 8547623..90d3421 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -11,6 +11,8 @@ builder.Services.AddScoped, Repository>(); builder.Services.AddScoped, Repository>(); builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); builder.Services.AddDbContext(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); From 49d5f074b5a9dc6b211368555df7c221ec42d3dc Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Mon, 27 Jan 2025 10:54:34 +0100 Subject: [PATCH 3/5] extension 2 --- exercise.pizzashopapi/DTO/OrderDto.cs | 2 +- exercise.pizzashopapi/Data/DataContext.cs | 1 - .../EndPoints/PizzaShopApi.cs | 20 +++++-- exercise.pizzashopapi/PizzaStage.cs | 53 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 exercise.pizzashopapi/PizzaStage.cs diff --git a/exercise.pizzashopapi/DTO/OrderDto.cs b/exercise.pizzashopapi/DTO/OrderDto.cs index 2a4e826..691817a 100644 --- a/exercise.pizzashopapi/DTO/OrderDto.cs +++ b/exercise.pizzashopapi/DTO/OrderDto.cs @@ -6,6 +6,6 @@ public class OrderDto public string Pizza { get; set; } public decimal Price { get; set; } public DateTime OrderedAt { get; set; } - public bool IsDelivered { get; set; } + public string Stage { get; set; } } } diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 176bab5..41e2bc8 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -14,7 +14,6 @@ public DataContext() } protected override void OnModelCreating(ModelBuilder modelBuilder) { - //Seeder seeder = new Seeder(); modelBuilder.Entity().HasKey(p => p.Id); modelBuilder.Entity().HasKey(c => c.Id); diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index 2d6c75e..2942553 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -21,6 +21,7 @@ public static void ConfigurePizzaShopApi(this WebApplication app) shop.MapGet("/pizzas/{id}", GetPizzaById); shop.MapGet("/toppings/{id}", GetTopping); shop.MapGet("/toppings", GetToppings); + shop.MapPost("/orders/{orderId}", DeliverOrder); } private static async Task GetPizzas(IRepository repository) @@ -51,7 +52,7 @@ private static async Task GetOrders(IRepository repository) Pizza = o.Pizza.Name, OrderedAt = o.OrderedAt, Price = o.Price, - IsDelivered = o.IsDelivered + Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString() })); } @@ -64,7 +65,7 @@ private static async Task GetOrdersByCustomerId(IRepository repo Pizza = o.Pizza.Name, OrderedAt = o.OrderedAt, Price = o.Price, - IsDelivered = o.IsDelivered + Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString() })); } @@ -129,8 +130,7 @@ private static async Task UpdateOrder(IRepository repository, in Customer = updatedOrder.Customer.Name, Pizza = updatedOrder.Pizza.Name, OrderedAt = updatedOrder.OrderedAt, - Price = updatedOrder.Price, - IsDelivered = updatedOrder.IsDelivered + Price = updatedOrder.Price }; return TypedResults.Ok(responseDto); } @@ -193,5 +193,17 @@ private static async Task GetToppings(IRepository repository) Price = t.Price })); } + + private static async Task DeliverOrder(IRepository repository, int id) + { + var order = await repository.GetById(id); + if (order == null) + { + return TypedResults.NotFound(); + } + order.IsDelivered = true; + await repository.Update(order); + return TypedResults.Ok("Order has been delivered"); + } } } diff --git a/exercise.pizzashopapi/PizzaStage.cs b/exercise.pizzashopapi/PizzaStage.cs new file mode 100644 index 0000000..14ea45b --- /dev/null +++ b/exercise.pizzashopapi/PizzaStage.cs @@ -0,0 +1,53 @@ +using System; + +namespace exercise.pizzashopapi +{ + public enum PizzaStage + { + Preparing, + Baking, + Delivering, + Delivered + } + public static class PizzaStageCalculor + { + public static PizzaStage GetPizzaStage(this DateTime orderTime) + { + var elapsed = DateTime.UtcNow - orderTime; + + if (elapsed.TotalMinutes < 3) + { + return PizzaStage.Preparing; + } + else if (elapsed.TotalMinutes < 15) + { + return PizzaStage.Baking; + } + else if (elapsed.TotalMinutes < 27) + { + return PizzaStage.Delivering; + } + else + { + return PizzaStage.Delivered; + } + } + + public static string GetPizzaStageString(this PizzaStage stage) + { + switch (stage) + { + case PizzaStage.Preparing: + return "Preparing"; + case PizzaStage.Baking: + return "Baking"; + case PizzaStage.Delivering: + return "Delivering"; + case PizzaStage.Delivered: + return "Delivered"; + default: + return "Lost, ops.."; + } + } + } +} From 99ff0339f127fe6a353305f94452b9d435591141 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Mon, 27 Jan 2025 11:19:32 +0100 Subject: [PATCH 4/5] extension 3 --- exercise.pizzashopapi/DTO/CreatedOrderDto.cs | 1 + exercise.pizzashopapi/DTO/OrderDto.cs | 1 + .../EndPoints/PizzaShopApi.cs | 10 ++++-- exercise.pizzashopapi/Models/Order.cs | 2 -- exercise.pizzashopapi/Utils/DeliveryEngine.cs | 34 +++++++++++++++++++ .../{ => Utils}/PizzaStage.cs | 4 +-- 6 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 exercise.pizzashopapi/Utils/DeliveryEngine.cs rename exercise.pizzashopapi/{ => Utils}/PizzaStage.cs (93%) diff --git a/exercise.pizzashopapi/DTO/CreatedOrderDto.cs b/exercise.pizzashopapi/DTO/CreatedOrderDto.cs index 4db68b5..c7f099b 100644 --- a/exercise.pizzashopapi/DTO/CreatedOrderDto.cs +++ b/exercise.pizzashopapi/DTO/CreatedOrderDto.cs @@ -8,6 +8,7 @@ public class CreatedOrderDto public decimal Price { get; set; } public bool IsDelivered { get; set; } public DateTime OrderedAt { get; set; } + public int EstimatedDelivery { get; set; } } } diff --git a/exercise.pizzashopapi/DTO/OrderDto.cs b/exercise.pizzashopapi/DTO/OrderDto.cs index 691817a..3825e2b 100644 --- a/exercise.pizzashopapi/DTO/OrderDto.cs +++ b/exercise.pizzashopapi/DTO/OrderDto.cs @@ -7,5 +7,6 @@ public class OrderDto public decimal Price { get; set; } public DateTime OrderedAt { get; set; } public string Stage { get; set; } + public int EstimatedDelivery { get; set; } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index 2942553..32c19e0 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,6 +1,7 @@ using exercise.pizzashopapi.DTO; using exercise.pizzashopapi.Models; using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Utils; using Microsoft.AspNetCore.Mvc; namespace exercise.pizzashopapi.EndPoints @@ -52,12 +53,14 @@ private static async Task GetOrders(IRepository repository) Pizza = o.Pizza.Name, OrderedAt = o.OrderedAt, Price = o.Price, - Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString() + Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString(), + EstimatedDelivery = DeliveryEngine.GetEstimatedDeliveryInMinutes(orders, o.Id) })); } private static async Task GetOrdersByCustomerId(IRepository repository, int customerId) { + var orders = await repository.GetWithIncludes(o => o.Customer, o => o.Pizza); return TypedResults.Ok(orders.Where(o => o.CustomerId == customerId).Select(o => new OrderDto() { @@ -65,13 +68,15 @@ private static async Task GetOrdersByCustomerId(IRepository repo Pizza = o.Pizza.Name, OrderedAt = o.OrderedAt, Price = o.Price, - Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString() + Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString(), + EstimatedDelivery = DeliveryEngine.GetEstimatedDeliveryInMinutes(orders, o.Id) })); } private static async Task CreateOrder(IRepository repository, IRepository pizzaRepository, IRepository toppingRepository, CreateOrderDto orderDto) { var pizza = await pizzaRepository.GetById(orderDto.PizzaId); + var orders = await repository.Get(); if (pizza == null) { return TypedResults.NotFound(); @@ -103,6 +108,7 @@ private static async Task CreateOrder(IRepository repository, IR OrderedAt = pizzaWithIncludes.OrderedAt, Price = pizzaWithIncludes.Price, IsDelivered = pizzaWithIncludes.IsDelivered, + EstimatedDelivery = DeliveryEngine.GetEstimatedDeliveryInMinutes(orders, orderedPizza.Id), Toppings = pizzaWithIncludes.Toppings.Select(t => new ToppingDto() { Name = t.Name, diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fe25ddb..15eb5c2 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -19,9 +19,7 @@ public class Order public bool IsDelivered { get; set; } = false; [Column("price")] public decimal Price { get; set; } - public List Toppings { get; set; } = new List(); - public List OrderToppings { get; set; } = new List(); } diff --git a/exercise.pizzashopapi/Utils/DeliveryEngine.cs b/exercise.pizzashopapi/Utils/DeliveryEngine.cs new file mode 100644 index 0000000..c0c6304 --- /dev/null +++ b/exercise.pizzashopapi/Utils/DeliveryEngine.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using exercise.pizzashopapi.Models; + +namespace exercise.pizzashopapi.Utils +{ + public static class DeliveryEngine + { + public static DateTime GetEstimatedDelivery(IEnumerable orders, int id) + { + var pendingOrders = orders.Where(order => !order.IsDelivered).ToList(); + + var orderIndex = pendingOrders.FindIndex(order => order.Id == id); + + int prepareTime = (orderIndex + 1) * 3; + + int bakeTime = (int)Math.Ceiling((double)(orderIndex + 1) / 4) * 12; + + int deliveryTime = (orderIndex + 1) * 10; + + int totalTime = prepareTime + bakeTime + deliveryTime; + + DateTime result = DateTime.UtcNow.AddMinutes(totalTime); + return result; + } + + //get the estimated delivery as minutes from now + public static int GetEstimatedDeliveryInMinutes(IEnumerable orders, int id) + { + return (int)(GetEstimatedDelivery(orders, id) - DateTime.UtcNow).TotalMinutes; + } + } +} diff --git a/exercise.pizzashopapi/PizzaStage.cs b/exercise.pizzashopapi/Utils/PizzaStage.cs similarity index 93% rename from exercise.pizzashopapi/PizzaStage.cs rename to exercise.pizzashopapi/Utils/PizzaStage.cs index 14ea45b..b1aa3f1 100644 --- a/exercise.pizzashopapi/PizzaStage.cs +++ b/exercise.pizzashopapi/Utils/PizzaStage.cs @@ -1,6 +1,6 @@ using System; -namespace exercise.pizzashopapi +namespace exercise.pizzashopapi.Utils { public enum PizzaStage { @@ -23,7 +23,7 @@ public static PizzaStage GetPizzaStage(this DateTime orderTime) { return PizzaStage.Baking; } - else if (elapsed.TotalMinutes < 27) + else if (elapsed.TotalMinutes < 25) { return PizzaStage.Delivering; } From f86aedfd77d86a8502c32983560e4cae76352e89 Mon Sep 17 00:00:00 2001 From: Andreas Conradi Nitschke Date: Mon, 27 Jan 2025 12:56:47 +0100 Subject: [PATCH 5/5] extension 4 --- exercise.pizzashopapi/Data/DataContext.cs | 2 + exercise.pizzashopapi/Data/Seeder.cs | 18 +- .../EndPoints/PizzaShopApi.cs | 28 ++ ...20250127114502_deliverydrivers.Designer.cs | 270 +++++++++++++++++ .../20250127114502_deliverydrivers.cs | 65 +++++ .../20250127114719_ordersfk.Designer.cs | 275 ++++++++++++++++++ .../Migrations/20250127114719_ordersfk.cs | 79 +++++ .../Migrations/DataContextModelSnapshot.cs | 38 +++ .../Models/DeliveryDriver.cs | 14 + exercise.pizzashopapi/Models/Order.cs | 2 + exercise.pizzashopapi/Program.cs | 1 + 11 files changed, 788 insertions(+), 4 deletions(-) create mode 100644 exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127114719_ordersfk.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127114719_ordersfk.cs create mode 100644 exercise.pizzashopapi/Models/DeliveryDriver.cs diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 41e2bc8..6553b00 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -22,6 +22,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasKey(t => t.Id); modelBuilder.Entity().HasKey(ot => new { ot.OrderId, ot.ToppingId }); modelBuilder.Entity().HasKey(o => o.Id); + modelBuilder.Entity().HasKey(d => d.Id); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -33,5 +34,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public DbSet Orders { get; set; } public DbSet Toppings { get; set; } public DbSet OrderToppings { get; set; } + public DbSet DeliveryDrivers { get; set; } } } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index 188dce8..fb8e8c9 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -23,19 +23,27 @@ public async static void SeedPizzaShopApi(this WebApplication app) await db.SaveChangesAsync(); } + if (!db.DeliveryDrivers.Any()) + { + db.Add(new DeliveryDriver() { Name = "Donald Driver" }); + db.Add(new DeliveryDriver() { Name = "Daisy Driver" }); + db.Add(new DeliveryDriver() { Name = "Daffy Driver" }); + db.Add(new DeliveryDriver() { Name = "Dennis Driver" }); + await db.SaveChangesAsync(); + } //order data if (!db.Orders.Any()) { - db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderedAt = new DateTime(2025, 1, 20, 8, 30, 0, DateTimeKind.Utc), Price = 10 }); - db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderedAt = new DateTime(2025, 1, 23, 19, 10, 0, DateTimeKind.Utc), Price = 15 }); - db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderedAt = new DateTime(2025, 1, 26, 16, 45, 0, DateTimeKind.Utc), Price = 8 }); + db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderedAt = new DateTime(2025, 1, 20, 8, 30, 0, DateTimeKind.Utc), Price = 10,DeliveryDriverId = 1 }); + db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderedAt = new DateTime(2025, 1, 23, 19, 10, 0, DateTimeKind.Utc), Price = 15, DeliveryDriverId = 2 }); + db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderedAt = new DateTime(2025, 1, 26, 16, 45, 0, DateTimeKind.Utc), Price = 8, DeliveryDriverId = 3 }); await db.SaveChangesAsync(); } if (!db.Toppings.Any()) { - db.Add(new Topping() { Name = "Bacon" , Price = 3}); + db.Add(new Topping() { Name = "Bacon", Price = 3 }); db.Add(new Topping() { Name = "Pineapple", Price = 2 }); db.Add(new Topping() { Name = "Vegan Cheese", Price = 1 }); db.Add(new Topping() { Name = "Mushrooms", Price = 1 }); @@ -51,6 +59,8 @@ public async static void SeedPizzaShopApi(this WebApplication app) db.Add(new OrderToppings() { OrderId = 3, ToppingId = 5 }); await db.SaveChangesAsync(); } + + } } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index 32c19e0..82671f4 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -23,6 +23,8 @@ public static void ConfigurePizzaShopApi(this WebApplication app) shop.MapGet("/toppings/{id}", GetTopping); shop.MapGet("/toppings", GetToppings); shop.MapPost("/orders/{orderId}", DeliverOrder); + shop.MapGet("drivers/{driverId}/orders", GetOrdersByDriverId); + shop.MapPut("/orders/{orderId}/{driverId}", AssignOrderToDriver); } private static async Task GetPizzas(IRepository repository) @@ -211,5 +213,31 @@ private static async Task DeliverOrder(IRepository repository, i await repository.Update(order); return TypedResults.Ok("Order has been delivered"); } + + private static async Task GetOrdersByDriverId(IRepository repository, int driverId) + { + var orders = await repository.GetWithIncludes(o => o.Customer, o => o.Pizza, o => o.DeliveryDriver); + return TypedResults.Ok(orders.Where(o => o.DeliveryDriverId == driverId).Select(o => new OrderDto() + { + Customer = o.Customer.Name, + Pizza = o.Pizza.Name, + OrderedAt = o.OrderedAt, + Price = o.Price, + Stage = o.OrderedAt.GetPizzaStage().GetPizzaStageString(), + EstimatedDelivery = DeliveryEngine.GetEstimatedDeliveryInMinutes(orders, o.Id) + })); + } + + private static async Task AssignOrderToDriver(IRepository repository, int orderId, int driverId) + { + var order = await repository.GetById(orderId); + if (order == null) + { + return TypedResults.NotFound(); + } + order.DeliveryDriverId = driverId; + await repository.Update(order); + return TypedResults.Ok("Order has been assigned to driver"); + } } } diff --git a/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.Designer.cs b/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.Designer.cs new file mode 100644 index 0000000..e541288 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.Designer.cs @@ -0,0 +1,270 @@ +// +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("20250127114502_deliverydrivers")] + partial class deliverydrivers + { + /// + 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("OrderTopping", b => + { + b.Property("OrdersId") + .HasColumnType("integer"); + + b.Property("ToppingsId") + .HasColumnType("integer"); + + b.HasKey("OrdersId", "ToppingsId"); + + b.HasIndex("ToppingsId"); + + b.ToTable("OrderTopping"); + }); + + 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.DeliveryDriver", 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("delivery_drivers"); + }); + + 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("DeliveryDriverId") + .HasColumnType("integer"); + + b.Property("IsDelivered") + .HasColumnType("boolean") + .HasColumnName("is_delivered"); + + b.Property("OrderedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ordered_at"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DeliveryDriverId"); + + b.HasIndex("PizzaId"); + + b.ToTable("orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + 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("order_toppings"); + }); + + 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.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("OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", null) + .WithMany() + .HasForeignKey("OrdersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + 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", null) + .WithMany("Orders") + .HasForeignKey("DeliveryDriverId"); + + 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.OrderToppings", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany("OrderToppings") + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Navigation("OrderToppings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.cs b/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.cs new file mode 100644 index 0000000..7eb1e3e --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114502_deliverydrivers.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class deliverydrivers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DeliveryDriverId", + table: "orders", + type: "integer", + nullable: true); + + migrationBuilder.CreateTable( + name: "delivery_drivers", + 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_delivery_drivers", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "IX_orders_DeliveryDriverId", + table: "orders", + column: "DeliveryDriverId"); + + migrationBuilder.AddForeignKey( + name: "FK_orders_delivery_drivers_DeliveryDriverId", + table: "orders", + column: "DeliveryDriverId", + principalTable: "delivery_drivers", + principalColumn: "id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_orders_delivery_drivers_DeliveryDriverId", + table: "orders"); + + migrationBuilder.DropTable( + name: "delivery_drivers"); + + migrationBuilder.DropIndex( + name: "IX_orders_DeliveryDriverId", + table: "orders"); + + migrationBuilder.DropColumn( + name: "DeliveryDriverId", + table: "orders"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.Designer.cs b/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.Designer.cs new file mode 100644 index 0000000..92cbc0e --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.Designer.cs @@ -0,0 +1,275 @@ +// +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("20250127114719_ordersfk")] + partial class ordersfk + { + /// + 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("OrderTopping", b => + { + b.Property("OrdersId") + .HasColumnType("integer"); + + b.Property("ToppingsId") + .HasColumnType("integer"); + + b.HasKey("OrdersId", "ToppingsId"); + + b.HasIndex("ToppingsId"); + + b.ToTable("OrderTopping"); + }); + + 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.DeliveryDriver", 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("delivery_drivers"); + }); + + 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("DeliveryDriverId") + .HasColumnType("integer") + .HasColumnName("delivery_driver_id"); + + b.Property("IsDelivered") + .HasColumnType("boolean") + .HasColumnName("is_delivered"); + + b.Property("OrderedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ordered_at"); + + b.Property("PizzaId") + .HasColumnType("integer") + .HasColumnName("pizza_id"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("DeliveryDriverId"); + + b.HasIndex("PizzaId"); + + b.ToTable("orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.OrderToppings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + 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("order_toppings"); + }); + + 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.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("OrderTopping", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", null) + .WithMany() + .HasForeignKey("OrdersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + 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("Orders") + .HasForeignKey("DeliveryDriverId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + 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.OrderToppings", b => + { + b.HasOne("exercise.pizzashopapi.Models.Order", "Order") + .WithMany("OrderToppings") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", "Topping") + .WithMany("OrderToppings") + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Topping"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.Navigation("OrderToppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Topping", b => + { + b.Navigation("OrderToppings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.cs b/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.cs new file mode 100644 index 0000000..d88481b --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127114719_ordersfk.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class ordersfk : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_orders_delivery_drivers_DeliveryDriverId", + table: "orders"); + + migrationBuilder.RenameColumn( + name: "DeliveryDriverId", + table: "orders", + newName: "delivery_driver_id"); + + migrationBuilder.RenameIndex( + name: "IX_orders_DeliveryDriverId", + table: "orders", + newName: "IX_orders_delivery_driver_id"); + + migrationBuilder.AlterColumn( + name: "delivery_driver_id", + table: "orders", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "FK_orders_delivery_drivers_delivery_driver_id", + table: "orders", + column: "delivery_driver_id", + principalTable: "delivery_drivers", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_orders_delivery_drivers_delivery_driver_id", + table: "orders"); + + migrationBuilder.RenameColumn( + name: "delivery_driver_id", + table: "orders", + newName: "DeliveryDriverId"); + + migrationBuilder.RenameIndex( + name: "IX_orders_delivery_driver_id", + table: "orders", + newName: "IX_orders_DeliveryDriverId"); + + migrationBuilder.AlterColumn( + name: "DeliveryDriverId", + table: "orders", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AddForeignKey( + name: "FK_orders_delivery_drivers_DeliveryDriverId", + table: "orders", + column: "DeliveryDriverId", + principalTable: "delivery_drivers", + principalColumn: "id"); + } + } +} diff --git a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs index d8cf8c9..4157ba5 100644 --- a/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -56,6 +56,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("customers"); }); + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", 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("delivery_drivers"); + }); + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => { b.Property("Id") @@ -69,6 +88,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("integer") .HasColumnName("customer_id"); + b.Property("DeliveryDriverId") + .HasColumnType("integer") + .HasColumnName("delivery_driver_id"); + b.Property("IsDelivered") .HasColumnType("boolean") .HasColumnName("is_delivered"); @@ -89,6 +112,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("CustomerId"); + b.HasIndex("DeliveryDriverId"); + b.HasIndex("PizzaId"); b.ToTable("orders"); @@ -189,6 +214,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("exercise.pizzashopapi.Models.DeliveryDriver", "DeliveryDriver") + .WithMany("Orders") + .HasForeignKey("DeliveryDriverId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("exercise.pizzashopapi.Models.Pizza", "Pizza") .WithMany() .HasForeignKey("PizzaId") @@ -197,6 +228,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Customer"); + b.Navigation("DeliveryDriver"); + b.Navigation("Pizza"); }); @@ -219,6 +252,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Topping"); }); + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Navigation("Orders"); + }); + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => { b.Navigation("OrderToppings"); diff --git a/exercise.pizzashopapi/Models/DeliveryDriver.cs b/exercise.pizzashopapi/Models/DeliveryDriver.cs new file mode 100644 index 0000000..5ba64bc --- /dev/null +++ b/exercise.pizzashopapi/Models/DeliveryDriver.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.pizzashopapi.Models +{ + [Table("delivery_drivers")] + public class DeliveryDriver + { + [Column("id")] + public int Id { get; set; } + [Column("name")] + public string Name { get; set; } + public IEnumerable Orders { get; set; } = new List(); + } +} diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index 15eb5c2..240aca2 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -13,6 +13,8 @@ public class Order [Column("pizza_id")] public int PizzaId { get; set; } public Pizza Pizza { get; set; } + public int? DeliveryDriverId { get; set; } = 1; + public DeliveryDriver DeliveryDriver { get; set; } [Column("ordered_at")] public DateTime OrderedAt { get; set; } = DateTime.UtcNow; [Column("is_delivered")] diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index 90d3421..0c02bb4 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -13,6 +13,7 @@ builder.Services.AddScoped, Repository>(); builder.Services.AddScoped, Repository>(); builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); builder.Services.AddDbContext(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer();