From 9edc0caa4d13b0c1144d0eb225e710d9acea2ba3 Mon Sep 17 00:00:00 2001 From: Kristian Sylte Date: Tue, 28 Jan 2025 09:09:06 +0100 Subject: [PATCH] Core + extension 1 and 4 --- exercise.pizzashopapi/Data/DataContext.cs | 59 ++++- exercise.pizzashopapi/Data/Seeder.cs | 43 +++- exercise.pizzashopapi/Dto/Customer.cs | 19 ++ exercise.pizzashopapi/Dto/DeliveryDriver.cs | 19 ++ exercise.pizzashopapi/Dto/OrderTopping.cs | 15 ++ exercise.pizzashopapi/Dto/Orders.cs | 67 ++++++ exercise.pizzashopapi/Dto/Pizza.cs | 7 + exercise.pizzashopapi/Dto/Topping.cs | 7 + .../EndPoints/PizzaShopApi.cs | 122 ++++++++++- .../20250127134822_InitialCreate.Designer.cs | 206 ++++++++++++++++++ .../20250127134822_InitialCreate.cs | 175 +++++++++++++++ .../Migrations/DataContextModelSnapshot.cs | 203 +++++++++++++++++ exercise.pizzashopapi/Models/Customer.cs | 24 +- .../Models/DeliveryDriver.cs | 23 ++ exercise.pizzashopapi/Models/Order.cs | 65 +++++- exercise.pizzashopapi/Models/OrderToppings.cs | 30 +++ exercise.pizzashopapi/Models/Toppings.cs | 8 + .../Repository/DatabaseContext.cs | 0 .../Repository/IRepository.cs | 18 +- .../Repository/Repository.cs | 166 +++++++++++++- 20 files changed, 1247 insertions(+), 29 deletions(-) create mode 100644 exercise.pizzashopapi/Dto/Customer.cs create mode 100644 exercise.pizzashopapi/Dto/DeliveryDriver.cs create mode 100644 exercise.pizzashopapi/Dto/OrderTopping.cs create mode 100644 exercise.pizzashopapi/Dto/Orders.cs create mode 100644 exercise.pizzashopapi/Dto/Pizza.cs create mode 100644 exercise.pizzashopapi/Dto/Topping.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.Designer.cs create mode 100644 exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.cs create mode 100644 exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs create mode 100644 exercise.pizzashopapi/Models/DeliveryDriver.cs create mode 100644 exercise.pizzashopapi/Models/OrderToppings.cs create mode 100644 exercise.pizzashopapi/Models/Toppings.cs create mode 100644 exercise.pizzashopapi/Repository/DatabaseContext.cs diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..cb388d7 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -6,23 +6,76 @@ namespace exercise.pizzashopapi.Data public class DataContext : DbContext { private string connectionString; + public DataContext() { var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString"); + connectionString = configuration.GetValue( + "ConnectionStrings:DefaultConnectionString" + ); + this.Database.EnsureCreated(); + this.Database.SetConnectionString(connectionString); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(b => b.Id); + + modelBuilder.Entity().HasKey(b => b.Id); + modelBuilder + .Entity() + .HasMany(c => c.Orders) + .WithOne(o => o.Customer) + .HasForeignKey(o => o.CustomerId); + + modelBuilder.Entity().HasKey(b => b.Id); + modelBuilder + .Entity() + .HasOne(o => o.Pizza) + .WithMany() + .HasForeignKey(o => o.PizzaId); + modelBuilder + .Entity() + .HasOne(o => o.Customer) + .WithMany(c => c.Orders) + .HasForeignKey(o => o.CustomerId); + modelBuilder + .Entity() + .HasMany(o => o.OrderToppings) + .WithOne(t => t.Order) + .HasForeignKey(a => a.OrderId); + modelBuilder.Entity().HasKey(b => b.Id); + modelBuilder + .Entity() + .HasMany(d => d.Orders) + .WithOne(o => o.DeliveryDriver) + .HasForeignKey(o => o.DeliveryDriverId); + + modelBuilder.Entity().HasKey(b => b.Id); + modelBuilder + .Entity() + .HasOne(ot => ot.Topping) + .WithMany() + .HasForeignKey(a => a.ToppingId); + + modelBuilder.Entity().HasKey(b => b.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; } public DbSet Orders { get; set; } + public DbSet DeliveryDrivers { get; set; } + public DbSet OrderToppings { get; set; } + public DbSet Toppings { get; set; } } } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..7bcabe8 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -4,28 +4,57 @@ namespace exercise.pizzashopapi.Data { public static class Seeder { - public async static void SeedPizzaShopApi(this WebApplication app) + public static async 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="Nigel" }); + db.Add(new Customer() { Name = "Nigel" }); db.Add(new Customer() { Name = "Dave" }); await db.SaveChangesAsync(); } - if(!db.Pizzas.Any()) + if (!db.Pizzas.Any()) { db.Add(new Pizza() { Name = "Cheese & Pineapple" }); db.Add(new Pizza() { Name = "Vegan Cheese Tastic" }); await db.SaveChangesAsync(); + } + if (!db.DeliveryDrivers.Any()) + { + db.DeliveryDrivers.Add(new DeliveryDriver() { Name = "Driver 1" }); + db.DeliveryDrivers.Add(new DeliveryDriver() { Name = "Driver 2" }); + await db.SaveChangesAsync(); + } + if (!db.Orders.Any()) + { + db.Orders.Add( + new Order() + { + CustomerId = 1, + PizzaId = 1, + DeliveryDriverId = 1, + } + ); + await db.SaveChangesAsync(); + } + if (!db.Toppings.Any()) + { + db.Toppings.Add(new Topping() { Name = "Pineapple" }); + db.Toppings.Add(new Topping() { Name = "Bacon" }); + await db.SaveChangesAsync(); } - //order data - if(1==1) + if (!db.OrderToppings.Any()) { + db.OrderToppings.Add(new OrderToppings() { OrderId = 1, ToppingId = 1 }); + await db.SaveChangesAsync(); + } + //order data + if (1 == 1) + { await db.SaveChangesAsync(); } } diff --git a/exercise.pizzashopapi/Dto/Customer.cs b/exercise.pizzashopapi/Dto/Customer.cs new file mode 100644 index 0000000..7210fae --- /dev/null +++ b/exercise.pizzashopapi/Dto/Customer.cs @@ -0,0 +1,19 @@ +namespace exercise.pizzashopapi.Models; + +public class CustomerGetDto +{ + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable Orders { get; set; } +} + +public class CustomerPostDto +{ + public string Name { get; set; } +} + +public class CustomerShallow +{ + public int Id { get; set; } + public string Name { get; set; } +} diff --git a/exercise.pizzashopapi/Dto/DeliveryDriver.cs b/exercise.pizzashopapi/Dto/DeliveryDriver.cs new file mode 100644 index 0000000..ebaab0a --- /dev/null +++ b/exercise.pizzashopapi/Dto/DeliveryDriver.cs @@ -0,0 +1,19 @@ +namespace exercise.pizzashopapi.Models; + +public record DeliveryDriverPostDto +{ + public string Name { get; set; } +} + +public record DeliveryDriverGetDto +{ + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable Orders { get; set; } +} + +public record DeliveryDriverShallow +{ + public int Id { get; set; } + public string Name { get; set; } +} diff --git a/exercise.pizzashopapi/Dto/OrderTopping.cs b/exercise.pizzashopapi/Dto/OrderTopping.cs new file mode 100644 index 0000000..cc8e4ee --- /dev/null +++ b/exercise.pizzashopapi/Dto/OrderTopping.cs @@ -0,0 +1,15 @@ +namespace exercise.pizzashopapi.Models; + +public record OrderToppingsWithoutOrder +{ + public int Id { get; set; } + public int ToppingId { get; set; } + public Topping Topping { get; set; } +} + +public record OrderToppingsWithoutTopping +{ + public int Id { get; set; } + public int OrderId { get; set; } + public Order Order { get; set; } +} diff --git a/exercise.pizzashopapi/Dto/Orders.cs b/exercise.pizzashopapi/Dto/Orders.cs new file mode 100644 index 0000000..fb51f35 --- /dev/null +++ b/exercise.pizzashopapi/Dto/Orders.cs @@ -0,0 +1,67 @@ +namespace exercise.pizzashopapi.Models; + +public record OrderGetDto +{ + public int Id { get; set; } + + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + + public IEnumerable OrderToppings { get; set; } + + public int CustomerId { get; set; } + public CustomerShallow Customer { get; set; } + + public int DeliveryDriverId { get; set; } + public DeliveryDriverShallow DeliveryDriver { get; set; } +} + +public record OrderPostDto +{ + public int PizzaId { get; set; } + public IEnumerable ToppingIds { get; set; } + public int CustomerId { get; set; } + public int DeliveryDriverId { get; set; } +} + +public record OrderShallow +{ + public int Id { get; set; } + + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + + public IEnumerable OrderToppings { get; set; } + + public int CustomerId { get; set; } + public Customer Customer { get; set; } + + public int DeliveryDriverId { get; set; } + public DeliveryDriver DeliveryDriver { get; set; } +} + +public record OrderWithoutCustomer +{ + public int Id { get; set; } + + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + + public IEnumerable OrderToppings { get; set; } + + public int DeliveryDriverId { get; set; } + public DeliveryDriverShallow DeliveryDriver { get; set; } +} + +public record OrderWithoutDriver +{ + public int Id { get; set; } + + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + + public IEnumerable OrderToppings { get; set; } + + public int CustomerId { get; set; } + public CustomerShallow Customer { get; set; } +} diff --git a/exercise.pizzashopapi/Dto/Pizza.cs b/exercise.pizzashopapi/Dto/Pizza.cs new file mode 100644 index 0000000..ea261ed --- /dev/null +++ b/exercise.pizzashopapi/Dto/Pizza.cs @@ -0,0 +1,7 @@ +namespace exercise.pizzashopapi.Models; + +public class PizzaPostDto +{ + public string Name { get; set; } + public decimal Price { get; set; } +} diff --git a/exercise.pizzashopapi/Dto/Topping.cs b/exercise.pizzashopapi/Dto/Topping.cs new file mode 100644 index 0000000..07284e0 --- /dev/null +++ b/exercise.pizzashopapi/Dto/Topping.cs @@ -0,0 +1,7 @@ +namespace exercise.pizzashopapi.Models; + +public class ToppingDto +{ + public string Name { get; set; } + public decimal Price { get; set; } +} diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..de931b6 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,15 +1,123 @@ -using exercise.pizzashopapi.Repository; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository; using Microsoft.AspNetCore.Mvc; -namespace exercise.pizzashopapi.EndPoints +namespace exercise.pizzashopapi.EndPoints; + +public static class PizzaShopApi { - public static class PizzaShopApi + public static void ConfigurePizzaShopApi(this WebApplication app) + { + app.MapGet("/customers", GetAllCustomers); + app.MapGet("/customers/{id}", GetCustomer); + app.MapPost("/customers", CreateCustomer); + + app.MapGet("/drivers", GetAllDrivers); + app.MapGet("/drivers/{id}", GetDriver); + app.MapPost("/drivers", CreateDriver); + + app.MapGet("/orders", GetAllOrders); + app.MapGet("/ordersbycustomer/{id}", GetOrdersByCustomer); + app.MapGet("/ordersbydriver/{id}", GetOrdersByDriver); + app.MapPost("/orders", CreateOrder); + + app.MapGet("/pizzas", GetPizzas); + app.MapPost("/pizzas", CreatePizza); + + app.MapGet("/toppings", GetToppings); + app.MapPost("/toppings", CreateTopping); + } + + public static async Task GetAllCustomers(IRepository repo) + { + var result = await repo.GetCustomers(); + return TypedResults.Ok(result.Select(o => o.ToGetDto())); + } + + public static async Task GetCustomer(IRepository repo, int id) + { + var result = await repo.GetCustomer(id); + return NullableResult(result?.ToGetDto()); + } + + public static async Task CreateCustomer(IRepository repo, CustomerPostDto c) + { + var result = await repo.CreateCustomer(c); + return NullableResult(result?.ToGetDto()); + } + + public static async Task GetAllDrivers(IRepository repo) + { + var result = await repo.GetDeliveryDrivers(); + return TypedResults.Ok(result.Select(o => o.ToGetDto())); + } + + public static async Task GetDriver(IRepository repo, int id) + { + var result = await repo.GetDeliveryDriver(id); + return NullableResult(result?.ToGetDto()); + } + + public static async Task CreateDriver(IRepository repo, DeliveryDriverPostDto d) + { + var result = await repo.CreateDeliveryDriver(d); + return NullableResult(result?.ToGetDto()); + } + + public static async Task GetAllOrders(IRepository repo) { - public static void ConfigurePizzaShopApi(this WebApplication app) + var result = await repo.GetOrders(); + return TypedResults.Ok(result.Select(o => o.ToGetDto())); + } + + public static async Task GetOrdersByCustomer(IRepository repo, int id) + { + var result = await repo.GetOrdersByCustomer(id); + return TypedResults.Ok(result.Select(o => o.ToGetDto())); + } + + public static async Task GetOrdersByDriver(IRepository repo, int id) + { + var result = await repo.GetOrdersByDriver(id); + return TypedResults.Ok(result.Select(o => o.ToGetDto())); + } + + public static async Task CreateOrder(IRepository repo, OrderPostDto order) + { + var result = await repo.CreateOrder(order); + return NullableResult(result?.ToGetDto()); + } + + public static async Task GetPizzas(IRepository repo) + { + var result = await repo.GetPizzas(); + return TypedResults.Ok(result); + } + + public static async Task CreatePizza(IRepository repo, PizzaPostDto pizza) + { + var result = await repo.CreatePizza(pizza); + return NullableResult(result); + } + + public static async Task GetToppings(IRepository repo) + { + var result = await repo.GetToppings(); + return TypedResults.Ok(result); + } + + public static async Task CreateTopping(IRepository repo, ToppingDto topping) + { + var result = await repo.CreateTopping(topping); + return NullableResult(result); + } + + private static IResult NullableResult(object? o) + { + if (o == null) { - + return TypedResults.NotFound(); } - - + return TypedResults.Ok(o); } } diff --git a/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.Designer.cs b/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.Designer.cs new file mode 100644 index 0000000..f564979 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.Designer.cs @@ -0,0 +1,206 @@ +// +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("20250127134822_InitialCreate")] + partial class InitialCreate + { + /// + 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.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("PizzaId") + .HasColumnType("integer"); + + 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"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ToppingId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ToppingId"); + + b.ToTable("OrderToppings"); + }); + + 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.Topping", 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("Toppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.DeliveryDriver", "DeliveryDriver") + .WithMany("Orders") + .HasForeignKey("DeliveryDriverId") + .OnDelete(DeleteBehavior.Cascade) + .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", null) + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Navigation("Orders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.cs b/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.cs new file mode 100644 index 0000000..733d02a --- /dev/null +++ b/exercise.pizzashopapi/Migrations/20250127134822_InitialCreate.cs @@ -0,0 +1,175 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace exercise.pizzashopapi.Migrations +{ + /// + public partial class InitialCreate : 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: "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: "Toppings", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Toppings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PizzaId = table.Column(type: "integer", nullable: false), + CustomerId = table.Column(type: "integer", nullable: false), + DeliveryDriverId = table.Column(type: "integer", 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", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Orders_Pizzas_PizzaId", + column: x => x.PizzaId, + principalTable: "Pizzas", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderToppings", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + OrderId = table.Column(type: "integer", nullable: false), + ToppingId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderToppings", x => x.Id); + table.ForeignKey( + name: "FK_OrderToppings_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderToppings_Toppings_ToppingId", + column: x => x.ToppingId, + principalTable: "Toppings", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + 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"); + + migrationBuilder.CreateIndex( + name: "IX_OrderToppings_OrderId", + table: "OrderToppings", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderToppings_ToppingId", + table: "OrderToppings", + column: "ToppingId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrderToppings"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "Toppings"); + + 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..331f6d7 --- /dev/null +++ b/exercise.pizzashopapi/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,203 @@ +// +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.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("PizzaId") + .HasColumnType("integer"); + + 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"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("ToppingId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ToppingId"); + + b.ToTable("OrderToppings"); + }); + + 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.Topping", 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("Toppings"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Order", b => + { + b.HasOne("exercise.pizzashopapi.Models.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.DeliveryDriver", "DeliveryDriver") + .WithMany("Orders") + .HasForeignKey("DeliveryDriverId") + .OnDelete(DeleteBehavior.Cascade) + .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", null) + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("exercise.pizzashopapi.Models.Topping", null) + .WithMany() + .HasForeignKey("ToppingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.Customer", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("exercise.pizzashopapi.Models.DeliveryDriver", b => + { + b.Navigation("Orders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/exercise.pizzashopapi/Models/Customer.cs b/exercise.pizzashopapi/Models/Customer.cs index 2ca83bd..d383b56 100644 --- a/exercise.pizzashopapi/Models/Customer.cs +++ b/exercise.pizzashopapi/Models/Customer.cs @@ -1,10 +1,26 @@ using System.ComponentModel.DataAnnotations.Schema; -namespace exercise.pizzashopapi.Models +namespace exercise.pizzashopapi.Models; + +public class Customer { - public class Customer + public int Id { get; set; } + public string Name { get; set; } + + public IEnumerable Orders { get; set; } + + public CustomerShallow ToShallow() + { + return new CustomerShallow() { Name = this.Name, Id = this.Id }; + } + + public CustomerGetDto ToGetDto() { - public int Id { get; set; } - public string Name { get; set; } + return new CustomerGetDto() + { + Id = this.Id, + Name = this.Name, + Orders = this.Orders.Select(o => o.WithoutCustomer()), + }; } } diff --git a/exercise.pizzashopapi/Models/DeliveryDriver.cs b/exercise.pizzashopapi/Models/DeliveryDriver.cs new file mode 100644 index 0000000..1c20bfa --- /dev/null +++ b/exercise.pizzashopapi/Models/DeliveryDriver.cs @@ -0,0 +1,23 @@ +namespace exercise.pizzashopapi.Models; + +public record DeliveryDriver +{ + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable Orders { get; set; } + + public DeliveryDriverShallow ToShallow() + { + return new DeliveryDriverShallow { Id = this.Id, Name = this.Name }; + } + + public DeliveryDriverGetDto ToGetDto() + { + return new DeliveryDriverGetDto + { + Id = this.Id, + Name = this.Name, + Orders = this.Orders.Select(o => o.WithoutDriver()), + }; + } +} diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..5fa06f3 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -1,10 +1,67 @@ using System.ComponentModel.DataAnnotations.Schema; -namespace exercise.pizzashopapi.Models +namespace exercise.pizzashopapi.Models; + +public class Order { - public class Order + public int Id { get; set; } + + public int PizzaId { get; set; } + public Pizza Pizza { get; set; } + + public IEnumerable OrderToppings { get; set; } + + public int CustomerId { get; set; } + public Customer Customer { get; set; } + + public int DeliveryDriverId { get; set; } + public DeliveryDriver DeliveryDriver { get; set; } + + public OrderWithoutCustomer WithoutCustomer() { - - + return new OrderWithoutCustomer() + { + Id = this.Id, + PizzaId = this.PizzaId, + Pizza = this.Pizza, + + OrderToppings = this.OrderToppings.Select(ot => ot.WithoutOrder()), + + DeliveryDriverId = this.DeliveryDriverId, + DeliveryDriver = this.DeliveryDriver.ToShallow(), + }; + } + + public OrderWithoutDriver WithoutDriver() + { + return new OrderWithoutDriver() + { + Id = this.Id, + PizzaId = this.PizzaId, + Pizza = this.Pizza, + + OrderToppings = this.OrderToppings.Select(ot => ot.WithoutOrder()), + + CustomerId = this.CustomerId, + Customer = this.Customer.ToShallow(), + }; + } + + public OrderGetDto ToGetDto() + { + return new OrderGetDto() + { + Id = this.Id, + + PizzaId = this.PizzaId, + Pizza = this.Pizza, + OrderToppings = this.OrderToppings.Select(ot => ot.WithoutOrder()), + + CustomerId = this.CustomerId, + Customer = this.Customer.ToShallow(), + + DeliveryDriverId = this.DeliveryDriverId, + DeliveryDriver = this.DeliveryDriver.ToShallow(), + }; } } diff --git a/exercise.pizzashopapi/Models/OrderToppings.cs b/exercise.pizzashopapi/Models/OrderToppings.cs new file mode 100644 index 0000000..d1ef7c5 --- /dev/null +++ b/exercise.pizzashopapi/Models/OrderToppings.cs @@ -0,0 +1,30 @@ +namespace exercise.pizzashopapi.Models; + +public record OrderToppings +{ + public int Id { get; set; } + public int OrderId { get; set; } + public Order Order { get; set; } + public int ToppingId { get; set; } + public Topping Topping { get; set; } + + public OrderToppingsWithoutTopping WithoutTopping() + { + return new OrderToppingsWithoutTopping() + { + Id = this.Id, + OrderId = this.OrderId, + Order = this.Order, + }; + } + + public OrderToppingsWithoutOrder WithoutOrder() + { + return new OrderToppingsWithoutOrder() + { + Id = this.Id, + ToppingId = this.ToppingId, + Topping = this.Topping, + }; + } +} diff --git a/exercise.pizzashopapi/Models/Toppings.cs b/exercise.pizzashopapi/Models/Toppings.cs new file mode 100644 index 0000000..4879187 --- /dev/null +++ b/exercise.pizzashopapi/Models/Toppings.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.Models; + +public record Topping +{ + public int Id { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } +} diff --git a/exercise.pizzashopapi/Repository/DatabaseContext.cs b/exercise.pizzashopapi/Repository/DatabaseContext.cs new file mode 100644 index 0000000..e69de29 diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index b114ea8..5025485 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -4,8 +4,24 @@ namespace exercise.pizzashopapi.Repository { public interface IRepository { + // TODO: move Dto out of Repository layer + Task> GetCustomers(); + Task GetCustomer(int id); + Task CreateCustomer(CustomerPostDto customer); + + Task> GetDeliveryDrivers(); + Task GetDeliveryDriver(int id); + Task CreateDeliveryDriver(DeliveryDriverPostDto deliveryDriver); + Task> GetOrdersByCustomer(int id); - + Task> GetOrdersByDriver(int id); + Task> GetOrders(); + Task CreateOrder(OrderPostDto order); + + Task> GetPizzas(); + Task CreatePizza(PizzaPostDto pizza); + Task> GetToppings(); + Task CreateTopping(ToppingDto topping); } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index e109fce..6f2f69b 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -1,14 +1,174 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.Models; +using Microsoft.EntityFrameworkCore; namespace exercise.pizzashopapi.Repository { public class Repository : IRepository { - private DataContext _db; - public Task> GetOrdersByCustomer(int id) + private DataContext db; + + public Repository(DataContext db) + { + this.db = db; + } + + public async Task CreateCustomer(CustomerPostDto customer) + { + var c = new Customer() { Name = customer.Name }; + var result = db.Customers.Add(c); + await db.SaveChangesAsync(); + return await GetCustomer(result.Entity.Id); + } + + public async Task CreateDeliveryDriver( + DeliveryDriverPostDto deliveryDriver + ) + { + var driver = new DeliveryDriver() { Name = deliveryDriver.Name }; + var result = db.DeliveryDrivers.Add(driver); + await db.SaveChangesAsync(); + return await GetDeliveryDriver(result.Entity.Id); + } + + public async Task CreateOrder(OrderPostDto order) + { + var o = new Order() + { + PizzaId = order.PizzaId, + CustomerId = order.CustomerId, + DeliveryDriverId = order.DeliveryDriverId, + }; + var result = db.Orders.Add(o); + await db.SaveChangesAsync(); + int orderId = result.Entity.Id; + foreach (int toppingId in order.ToppingIds) + { + var pt = new OrderToppings() { OrderId = orderId, ToppingId = toppingId }; + db.OrderToppings.Add(pt); + } + await db.SaveChangesAsync(); + + return await db + .Orders.Include(o => o.Pizza) + .Include(o => o.DeliveryDriver) + .Include(o => o.Customer) + .Include(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .FirstOrDefaultAsync(o => o.Id == orderId); + } + + public async Task CreatePizza(PizzaPostDto pizza) + { + var p = new Pizza { Name = pizza.Name, Price = pizza.Price }; + var result = db.Pizzas.Add(p); + await db.SaveChangesAsync(); + return await db.Pizzas.FirstOrDefaultAsync(p => p.Id == result.Entity.Id); + } + + public async Task CreateTopping(ToppingDto topping) + { + var t = new Topping { Name = topping.Name, Price = topping.Price }; + var result = db.Toppings.Add(t); + await db.SaveChangesAsync(); + return await db.Toppings.FirstOrDefaultAsync(t => t.Id == result.Entity.Id); + } + + public async Task GetCustomer(int id) + { + return await db + .Customers.Include(c => c.Orders) + .ThenInclude(o => o.DeliveryDriver) + .Include(c => c.Orders) + .ThenInclude(o => o.Pizza) + .Include(c => c.Orders) + .ThenInclude(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .FirstOrDefaultAsync(c => c.Id == id); + } + + public async Task> GetCustomers() + { + return await db + .Customers.Include(c => c.Orders) + .ThenInclude(o => o.DeliveryDriver) + .Include(c => c.Orders) + .ThenInclude(o => o.Pizza) + .Include(c => c.Orders) + .ThenInclude(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .ToListAsync(); + } + + public async Task GetDeliveryDriver(int id) + { + return await db + .DeliveryDrivers.Include(c => c.Orders) + .ThenInclude(o => o.Customer) + .Include(c => c.Orders) + .ThenInclude(o => o.Pizza) + .Include(c => c.Orders) + .ThenInclude(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .FirstOrDefaultAsync(d => d.Id == id); + } + + public async Task> GetDeliveryDrivers() + { + return await db + .DeliveryDrivers.Include(c => c.Orders) + .ThenInclude(o => o.Customer) + .Include(c => c.Orders) + .ThenInclude(o => o.Pizza) + .Include(c => c.Orders) + .ThenInclude(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .ToListAsync(); + } + + public async Task> GetOrders() + { + return await db + .Orders.Include(o => o.Pizza) + .Include(o => o.DeliveryDriver) + .Include(o => o.Customer) + .Include(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .ToListAsync(); + } + + public async Task> GetOrdersByCustomer(int id) + { + return await db + .Orders.Include(o => o.DeliveryDriver) + .Include(o => o.Pizza) + .Include(o => o.Customer) + .Include(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .Where(o => o.CustomerId == id) + .ToListAsync(); + } + + public async Task> GetOrdersByDriver(int id) + { + return await db + .Orders.Include(o => o.DeliveryDriver) + .Include(o => o.Pizza) + .Include(o => o.Customer) + .Include(o => o.OrderToppings) + .ThenInclude(ot => ot.Topping) + .Where(o => o.DeliveryDriverId == id) + .ToListAsync(); + } + + public async Task> GetPizzas() + { + return await db.Pizzas.ToListAsync(); + } + + public async Task> GetToppings() { - throw new NotImplementedException(); + return await db.Toppings.ToListAsync(); } } }