From d2c9ec518714abf1496db41a70cb3f2d725caadb Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:17:12 +0100 Subject: [PATCH 1/5] Ignore migrations folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a62e968..77dd692 100644 --- a/.gitignore +++ b/.gitignore @@ -482,3 +482,5 @@ $RECYCLE.BIN/ */**/obj/Release /exercise.pizzashopapi/appsettings.json /exercise.pizzashopapi/appsettings.Development.json + +/exercise.pizzashopapi/Migrations/* From 2e5e6becac5aca11f6f66326cf5575a21e4e442d Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:19:21 +0100 Subject: [PATCH 2/5] First iteration of core --- exercise.pizzashopapi/DTO/CustomerResponse.cs | 7 +++ exercise.pizzashopapi/DTO/OrderResponse.cs | 12 ++++ exercise.pizzashopapi/DTO/PizzaResponse.cs | 8 +++ exercise.pizzashopapi/Data/DataContext.cs | 58 +++++++++++++------ exercise.pizzashopapi/Data/Seeder.cs | 15 +++-- .../EndPoints/PizzaShopApi.cs | 35 +++++++++-- .../Mapper/AutoMapperProfile.cs | 14 +++++ exercise.pizzashopapi/Models/Customer.cs | 1 + exercise.pizzashopapi/Models/Order.cs | 11 +++- exercise.pizzashopapi/Program.cs | 7 ++- .../Repository/IRepository.cs | 10 ++-- .../Repository/Repository.cs | 39 +++++++++---- .../exercise.pizzashopapi.csproj | 1 + 13 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 exercise.pizzashopapi/DTO/CustomerResponse.cs create mode 100644 exercise.pizzashopapi/DTO/OrderResponse.cs create mode 100644 exercise.pizzashopapi/DTO/PizzaResponse.cs create mode 100644 exercise.pizzashopapi/Mapper/AutoMapperProfile.cs diff --git a/exercise.pizzashopapi/DTO/CustomerResponse.cs b/exercise.pizzashopapi/DTO/CustomerResponse.cs new file mode 100644 index 0000000..0623c4c --- /dev/null +++ b/exercise.pizzashopapi/DTO/CustomerResponse.cs @@ -0,0 +1,7 @@ +namespace exercise.pizzashopapi.DTO; + +public class CustomerResponse +{ + public int Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/DTO/OrderResponse.cs b/exercise.pizzashopapi/DTO/OrderResponse.cs new file mode 100644 index 0000000..57de52e --- /dev/null +++ b/exercise.pizzashopapi/DTO/OrderResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace exercise.pizzashopapi.DTO; + +public class OrderResponse +{ + public int Id { get; set; } + public DateTime OrderDate { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public CustomerResponse? Customer { get; set; } + public PizzaResponse Pizza { get; set; } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/DTO/PizzaResponse.cs b/exercise.pizzashopapi/DTO/PizzaResponse.cs new file mode 100644 index 0000000..df6a8b9 --- /dev/null +++ b/exercise.pizzashopapi/DTO/PizzaResponse.cs @@ -0,0 +1,8 @@ +namespace exercise.pizzashopapi.DTO; + +public class PizzaResponse +{ + public int Id { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index 129199e..d355e64 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -1,28 +1,48 @@ -using exercise.pizzashopapi.Models; +using System.Diagnostics; +using exercise.pizzashopapi.Models; using Microsoft.EntityFrameworkCore; -namespace exercise.pizzashopapi.Data +namespace exercise.pizzashopapi.Data; + +public class DataContext : DbContext { - public class DataContext : DbContext + private readonly string _connectionString; + + public DataContext() { - private string connectionString; - public DataContext() - { - var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); - connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString"); + var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + _connectionString = configuration.GetValue("ConnectionStrings:DefaultConnectionString")!; + } - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql(connectionString); + public DbSet Pizzas { get; set; } + public DbSet Customers { get; set; } + public DbSet Orders { get; set; } - //set primary of order? + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(o => o.Id); + modelBuilder.Entity() + .HasOne(o => o.Customer) + .WithMany(c => c.Orders) + .HasForeignKey(o => o.CustomerId); + modelBuilder.Entity() + .HasOne(o => o.Pizza) + .WithMany() + .HasForeignKey(o => o.PizzaId); + modelBuilder.Entity() + .HasKey(c => c.Id); + modelBuilder.Entity() + .HasKey(p => p.Id); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql(_connectionString); + optionsBuilder.LogTo(message => Debug.WriteLine(message)); - //seed data? + //set primary of order? - } - public DbSet Pizzas { get; set; } - public DbSet Customers { get; set; } - public DbSet Orders { get; set; } + //seed data? } -} +} \ No newline at end of file diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index f87fbef..08008e5 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -10,22 +10,21 @@ 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() { Id = 1, Name="Nigel" }); + db.Add(new Customer() { Id = 2, Name = "Dave" }); 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() { Id = 1, Name = "Cheese & Pineapple" }); + db.Add(new Pizza() { Id = 2, Name = "Vegan Cheese Tastic" }); await db.SaveChangesAsync(); - } - //order data - if(1==1) + if(!db.Orders.Any()) { - + db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); + db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); await db.SaveChangesAsync(); } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index f8be2b0..ea85ea8 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -1,5 +1,7 @@ -using exercise.pizzashopapi.Repository; -using Microsoft.AspNetCore.Mvc; +using AutoMapper; +using exercise.pizzashopapi.DTO; +using exercise.pizzashopapi.Models; +using exercise.pizzashopapi.Repository; namespace exercise.pizzashopapi.EndPoints { @@ -7,9 +9,34 @@ public static class PizzaShopApi { public static void ConfigurePizzaShopApi(this WebApplication app) { - + var ordergroup = app.MapGroup("orders"); + ordergroup.MapGet("/", GetOrders); + ordergroup.MapGet("/{id}", GetOrder); + ordergroup.MapGet("/customer/{id}", GetOrdersByCustomerId); } - + private static async Task GetOrders(IRepository repository, IMapper mapper) + { + var orders = await repository.GetAll(o => o.Customer, o => o.Pizza); + var response = mapper.Map>(orders); + + return TypedResults.Ok(response); + } + + private static async Task GetOrder(IRepository repository, IMapper mapper, int id) + { + var order = await repository.Get(o => o.Id == id, o => o.Customer, o => o.Pizza); + var response = mapper.Map(order); + + return TypedResults.Ok(response); + } + + private static async Task GetOrdersByCustomerId(IRepository repository, IMapper mapper, int id) + { + var orders = await repository.GetAll(o => o.CustomerId == id, o => o.Pizza); + var response = mapper.Map>(orders); + + return TypedResults.Ok(response); + } } } diff --git a/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs b/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs new file mode 100644 index 0000000..d0d8b6b --- /dev/null +++ b/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; + +namespace exercise.pizzashopapi.Mapper; + +public class AutoMapperProfile : Profile +{ + public AutoMapperProfile() + { + CreateMap() + .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)); + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/Models/Customer.cs b/exercise.pizzashopapi/Models/Customer.cs index 2ca83bd..76ca184 100644 --- a/exercise.pizzashopapi/Models/Customer.cs +++ b/exercise.pizzashopapi/Models/Customer.cs @@ -6,5 +6,6 @@ public class Customer { public int Id { get; set; } public string Name { get; set; } + public virtual List? Orders { get; set; } } } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index fbe6113..4cd5673 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -4,7 +4,16 @@ namespace exercise.pizzashopapi.Models { public class Order { - + public int Id { get; set; } + public int? CustomerId { get; set; } + public int PizzaId { get; set; } + public Customer? Customer { get; set; } + public Pizza Pizza { get; set; } + public DateTime OrderDate { get; set; } + public Order() + { + OrderDate = DateTime.UtcNow; + } } } diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index c04a440..5df2d46 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -1,5 +1,7 @@ using exercise.pizzashopapi.Data; using exercise.pizzashopapi.EndPoints; +using exercise.pizzashopapi.Mapper; +using exercise.pizzashopapi.Models; using exercise.pizzashopapi.Repository; var builder = WebApplication.CreateBuilder(args); @@ -7,11 +9,14 @@ // 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(); builder.Services.AddSwaggerGen(); +builder.Services.AddAutoMapper(typeof(AutoMapperProfile)); var app = builder.Build(); diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index b114ea8..40c71de 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -1,11 +1,11 @@ -using exercise.pizzashopapi.Models; +using System.Linq.Expressions; namespace exercise.pizzashopapi.Repository { - public interface IRepository + public interface IRepository where T : class { - Task> GetOrdersByCustomer(int id); - - + Task> GetAll(params Expression>[] includes); + Task> GetAll(Expression> predicate, params Expression>[] includes); + Task Get(Expression> predicate, params Expression>[] includes); } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index e109fce..5d28fc0 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -1,14 +1,33 @@ -using exercise.pizzashopapi.Data; -using exercise.pizzashopapi.Models; +using System.Linq.Expressions; +using exercise.pizzashopapi.Data; +using Microsoft.EntityFrameworkCore; -namespace exercise.pizzashopapi.Repository +namespace exercise.pizzashopapi.Repository; + +public class Repository(DataContext db) : IRepository where T : class { - public class Repository : IRepository + public async Task> GetAll(params Expression>[] includes) + { + IQueryable query = db.Set(); + foreach (var expression in includes) query = query.Include(expression); + + return await query.ToListAsync(); + } + + public async Task> GetAll(Expression> predicate, + params Expression>[] includes) { - private DataContext _db; - public Task> GetOrdersByCustomer(int id) - { - throw new NotImplementedException(); - } + IQueryable query = db.Set(); + foreach (var expression in includes) query = query.Include(expression); + + return await query.Where(predicate).ToListAsync(); + } + + public async Task Get(Expression> predicate, params Expression>[] includes) + { + IQueryable query = db.Set(); + foreach (var expression in includes) query = query.Include(expression); + + return await query.FirstOrDefaultAsync(predicate); } -} +} \ No newline at end of file diff --git a/exercise.pizzashopapi/exercise.pizzashopapi.csproj b/exercise.pizzashopapi/exercise.pizzashopapi.csproj index 624203b..4a3bdd5 100644 --- a/exercise.pizzashopapi/exercise.pizzashopapi.csproj +++ b/exercise.pizzashopapi/exercise.pizzashopapi.csproj @@ -11,6 +11,7 @@ + all From 5280cb2000bd1d306f793eebffd170cdf3444780 Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:28:52 +0100 Subject: [PATCH 3/5] Added my own pizza order --- exercise.pizzashopapi/Data/DataContext.cs | 4 ---- exercise.pizzashopapi/Data/Seeder.cs | 7 +++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index d355e64..ad630d7 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -40,9 +40,5 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseNpgsql(_connectionString); optionsBuilder.LogTo(message => Debug.WriteLine(message)); - - //set primary of order? - - //seed data? } } \ No newline at end of file diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index 08008e5..19ac810 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -12,12 +12,14 @@ public async static void SeedPizzaShopApi(this WebApplication app) { db.Add(new Customer() { Id = 1, Name="Nigel" }); db.Add(new Customer() { Id = 2, Name = "Dave" }); + db.Add(new Customer() { Id = 3, Name = "Magnus" }); await db.SaveChangesAsync(); } if(!db.Pizzas.Any()) { - db.Add(new Pizza() { Id = 1, Name = "Cheese & Pineapple" }); - db.Add(new Pizza() { Id = 2, Name = "Vegan Cheese Tastic" }); + db.Add(new Pizza() { Id = 1, Name = "Cheese & Pineapple", Price = 9.99m }); + db.Add(new Pizza() { Id = 2, Name = "Vegan Cheese Tastic", Price = 12.99m }); + db.Add(new Pizza() { Id = 3, Name = "Mighty Meat", Price = 14.99m }); await db.SaveChangesAsync(); } @@ -25,6 +27,7 @@ public async static void SeedPizzaShopApi(this WebApplication app) { db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); + db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderDate = DateTime.Parse("2025-01-27").ToUniversalTime() }); await db.SaveChangesAsync(); } } From 8a40fd6b9e71d850e8f2f4b6a33c7a2f0ff8d85e Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:08:13 +0100 Subject: [PATCH 4/5] Topping extension completed --- exercise.pizzashopapi/DTO/OrderPut.cs | 6 +++ exercise.pizzashopapi/DTO/OrderResponse.cs | 1 + exercise.pizzashopapi/Data/DataContext.cs | 9 +++++ exercise.pizzashopapi/Data/Seeder.cs | 16 ++++++++ .../EndPoints/PizzaShopApi.cs | 40 +++++++++++++++++-- .../Mapper/AutoMapperProfile.cs | 3 +- exercise.pizzashopapi/Models/Order.cs | 6 +-- exercise.pizzashopapi/Models/Topping.cs | 7 ++++ exercise.pizzashopapi/Program.cs | 1 + .../Repository/IRepository.cs | 1 + .../Repository/Repository.cs | 7 ++++ 11 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 exercise.pizzashopapi/DTO/OrderPut.cs create mode 100644 exercise.pizzashopapi/Models/Topping.cs diff --git a/exercise.pizzashopapi/DTO/OrderPut.cs b/exercise.pizzashopapi/DTO/OrderPut.cs new file mode 100644 index 0000000..c5467a1 --- /dev/null +++ b/exercise.pizzashopapi/DTO/OrderPut.cs @@ -0,0 +1,6 @@ +namespace exercise.pizzashopapi.DTO; + +public class OrderPut +{ + public List ToppingIds { get; set; } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/DTO/OrderResponse.cs b/exercise.pizzashopapi/DTO/OrderResponse.cs index 57de52e..e1dafb6 100644 --- a/exercise.pizzashopapi/DTO/OrderResponse.cs +++ b/exercise.pizzashopapi/DTO/OrderResponse.cs @@ -9,4 +9,5 @@ public class OrderResponse [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public CustomerResponse? Customer { get; set; } public PizzaResponse Pizza { get; set; } + public IEnumerable Toppings { get; set; } = new List(); } \ No newline at end of file diff --git a/exercise.pizzashopapi/Data/DataContext.cs b/exercise.pizzashopapi/Data/DataContext.cs index ad630d7..14a0e56 100644 --- a/exercise.pizzashopapi/Data/DataContext.cs +++ b/exercise.pizzashopapi/Data/DataContext.cs @@ -17,6 +17,7 @@ public DataContext() public DbSet Pizzas { get; set; } public DbSet Customers { get; set; } public DbSet Orders { get; set; } + public DbSet Toppings { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -30,6 +31,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasOne(o => o.Pizza) .WithMany() .HasForeignKey(o => o.PizzaId); + modelBuilder.Entity() + .HasMany(o => o.Toppings) + .WithMany() + .UsingEntity>( + "OrderToppings", + r => r.HasOne().WithMany().HasForeignKey("ToppingId"), + l => l.HasOne().WithMany().HasForeignKey("OrderId") + ); modelBuilder.Entity() .HasKey(c => c.Id); modelBuilder.Entity() diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index 19ac810..738fb6b 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -22,12 +22,28 @@ public async static void SeedPizzaShopApi(this WebApplication app) db.Add(new Pizza() { Id = 3, Name = "Mighty Meat", Price = 14.99m }); await db.SaveChangesAsync(); } + + if (!db.Toppings.Any()) + { + db.Add(new Topping() { Id = 1, Name = "Bacon" }); + db.Add(new Topping() { Id = 2, Name = "Mushrooms" }); + db.Add(new Topping() { Id = 3, Name = "Olives" }); + await db.SaveChangesAsync(); + } if(!db.Orders.Any()) { db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderDate = DateTime.Parse("2025-01-27").ToUniversalTime() }); + + await db.SaveChangesAsync(); + + // Toppings for testing + var order = db.Orders.Find(2); + order.Toppings.Add(new Topping() { Id = 1, Name = "Bacon" }); + order.Toppings.Add(new Topping() { Id = 2, Name = "Mushrooms" }); + await db.SaveChangesAsync(); } } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index ea85ea8..06a31a1 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -2,6 +2,7 @@ using exercise.pizzashopapi.DTO; using exercise.pizzashopapi.Models; using exercise.pizzashopapi.Repository; +using Microsoft.AspNetCore.Mvc; namespace exercise.pizzashopapi.EndPoints { @@ -13,11 +14,12 @@ public static void ConfigurePizzaShopApi(this WebApplication app) ordergroup.MapGet("/", GetOrders); ordergroup.MapGet("/{id}", GetOrder); ordergroup.MapGet("/customer/{id}", GetOrdersByCustomerId); + ordergroup.MapPut("/{id}", UpdateOrder); } private static async Task GetOrders(IRepository repository, IMapper mapper) { - var orders = await repository.GetAll(o => o.Customer, o => o.Pizza); + var orders = await repository.GetAll(o => o.Customer, o => o.Pizza, o => o.Toppings); var response = mapper.Map>(orders); return TypedResults.Ok(response); @@ -25,7 +27,7 @@ private static async Task GetOrders(IRepository repository, IMap private static async Task GetOrder(IRepository repository, IMapper mapper, int id) { - var order = await repository.Get(o => o.Id == id, o => o.Customer, o => o.Pizza); + var order = await repository.Get(o => o.Id == id, o => o.Customer, o => o.Pizza, o => o.Toppings); var response = mapper.Map(order); return TypedResults.Ok(response); @@ -33,10 +35,42 @@ private static async Task GetOrder(IRepository repository, IMapp private static async Task GetOrdersByCustomerId(IRepository repository, IMapper mapper, int id) { - var orders = await repository.GetAll(o => o.CustomerId == id, o => o.Pizza); + var orders = await repository.GetAll(o => o.CustomerId == id, o => o.Pizza, o => o.Toppings); var response = mapper.Map>(orders); return TypedResults.Ok(response); } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task UpdateOrder(IRepository orderRepository, IRepository toppingRepository, IMapper mapper, int id, [FromBody] OrderPut body) + { + var order = await orderRepository.Get(o => o.Id == id); + if (order == null) return TypedResults.NotFound(); + + var validToppings = new HashSet(); + foreach (var toppingId in body.ToppingIds) + { + var topping = await toppingRepository.Get(t => t.Id == toppingId); + if (topping != null) + { + validToppings.Add(topping); + } + } + + foreach (var topping in validToppings) + { + if (order.Toppings is not null && !order.Toppings.Contains(topping)) + { + return TypedResults.BadRequest("Can't remove already added toppings."); + } + } + + order.Toppings = validToppings.ToList(); + await orderRepository.Update(order); + + return TypedResults.NoContent(); + } } } diff --git a/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs b/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs index d0d8b6b..c38f2dd 100644 --- a/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs +++ b/exercise.pizzashopapi/Mapper/AutoMapperProfile.cs @@ -7,7 +7,8 @@ public class AutoMapperProfile : Profile public AutoMapperProfile() { CreateMap() - .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)); + .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer)) + .ForMember(dest => dest.Toppings, opt => opt.MapFrom(src => src.Toppings.Select(t => t.Name))); CreateMap(); CreateMap(); } diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index 4cd5673..7690753 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace exercise.pizzashopapi.Models +namespace exercise.pizzashopapi.Models { public class Order { @@ -9,6 +7,8 @@ public class Order public int PizzaId { get; set; } public Customer? Customer { get; set; } public Pizza Pizza { get; set; } + //public List? ToppingIds { get; set; } + public List? Toppings { get; set; } public DateTime OrderDate { get; set; } public Order() diff --git a/exercise.pizzashopapi/Models/Topping.cs b/exercise.pizzashopapi/Models/Topping.cs new file mode 100644 index 0000000..4c5b211 --- /dev/null +++ b/exercise.pizzashopapi/Models/Topping.cs @@ -0,0 +1,7 @@ +namespace exercise.pizzashopapi.Models; + +public class Topping +{ + public int Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/exercise.pizzashopapi/Program.cs b/exercise.pizzashopapi/Program.cs index 5df2d46..7383e5c 100644 --- a/exercise.pizzashopapi/Program.cs +++ b/exercise.pizzashopapi/Program.cs @@ -12,6 +12,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(); diff --git a/exercise.pizzashopapi/Repository/IRepository.cs b/exercise.pizzashopapi/Repository/IRepository.cs index 40c71de..f64ebd7 100644 --- a/exercise.pizzashopapi/Repository/IRepository.cs +++ b/exercise.pizzashopapi/Repository/IRepository.cs @@ -7,5 +7,6 @@ public interface IRepository where T : class Task> GetAll(params Expression>[] includes); Task> GetAll(Expression> predicate, params Expression>[] includes); Task Get(Expression> predicate, params Expression>[] includes); + Task Update(T entity); } } diff --git a/exercise.pizzashopapi/Repository/Repository.cs b/exercise.pizzashopapi/Repository/Repository.cs index 5d28fc0..9345442 100644 --- a/exercise.pizzashopapi/Repository/Repository.cs +++ b/exercise.pizzashopapi/Repository/Repository.cs @@ -30,4 +30,11 @@ public async Task> GetAll(Expression> predicate, return await query.FirstOrDefaultAsync(predicate); } + + public async Task Update(T entity) + { + db.Set().Update(entity); + await db.SaveChangesAsync(); + return entity; + } } \ No newline at end of file From 12876aff7c7693324abcd588f64cdcb52469a88e Mon Sep 17 00:00:00 2001 From: magnus195 <22998711+magnus195@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:55:57 +0100 Subject: [PATCH 5/5] Second extension done --- exercise.pizzashopapi/DTO/OrderResponse.cs | 2 ++ exercise.pizzashopapi/Data/Seeder.cs | 13 ++++++---- .../EndPoints/PizzaShopApi.cs | 14 +++++++++++ exercise.pizzashopapi/Enums/OrderStatus.cs | 9 +++++++ exercise.pizzashopapi/Models/Order.cs | 25 +++++++++++++++++-- 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 exercise.pizzashopapi/Enums/OrderStatus.cs diff --git a/exercise.pizzashopapi/DTO/OrderResponse.cs b/exercise.pizzashopapi/DTO/OrderResponse.cs index e1dafb6..f538e47 100644 --- a/exercise.pizzashopapi/DTO/OrderResponse.cs +++ b/exercise.pizzashopapi/DTO/OrderResponse.cs @@ -1,10 +1,12 @@ using System.Text.Json.Serialization; +using exercise.pizzashopapi.Enums; namespace exercise.pizzashopapi.DTO; public class OrderResponse { public int Id { get; set; } + public string Status { get; set; } public DateTime OrderDate { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public CustomerResponse? Customer { get; set; } diff --git a/exercise.pizzashopapi/Data/Seeder.cs b/exercise.pizzashopapi/Data/Seeder.cs index 738fb6b..37ffecd 100644 --- a/exercise.pizzashopapi/Data/Seeder.cs +++ b/exercise.pizzashopapi/Data/Seeder.cs @@ -34,15 +34,18 @@ public async static void SeedPizzaShopApi(this WebApplication app) if(!db.Orders.Any()) { db.Add(new Order() { CustomerId = 1, PizzaId = 1, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); - db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime() }); - db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderDate = DateTime.Parse("2025-01-27").ToUniversalTime() }); + db.Add(new Order() { CustomerId = 2, PizzaId = 2, OrderDate = DateTime.Parse("2021-01-01").ToUniversalTime(), Delivered = true }); + var order = db.Add(new Order() { CustomerId = 3, PizzaId = 3, OrderDate = DateTime.Parse("2025-01-27").ToUniversalTime() }); await db.SaveChangesAsync(); // Toppings for testing - var order = db.Orders.Find(2); - order.Toppings.Add(new Topping() { Id = 1, Name = "Bacon" }); - order.Toppings.Add(new Topping() { Id = 2, Name = "Mushrooms" }); + //var order = db.Orders.Find(2); + order.Entity.Toppings = new List + { + await db.Toppings.FindAsync(1), + await db.Toppings.FindAsync(2) + }; await db.SaveChangesAsync(); } diff --git a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs index 06a31a1..66cc12f 100644 --- a/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs +++ b/exercise.pizzashopapi/EndPoints/PizzaShopApi.cs @@ -15,6 +15,7 @@ public static void ConfigurePizzaShopApi(this WebApplication app) ordergroup.MapGet("/{id}", GetOrder); ordergroup.MapGet("/customer/{id}", GetOrdersByCustomerId); ordergroup.MapPut("/{id}", UpdateOrder); + ordergroup.MapPost("/markDelivered/{id}", MarkOrderDelivered); } private static async Task GetOrders(IRepository repository, IMapper mapper) @@ -72,5 +73,18 @@ private static async Task UpdateOrder(IRepository orderRepositor return TypedResults.NoContent(); } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static async Task MarkOrderDelivered(IRepository repository, int id) + { + var order = await repository.Get(o => o.Id == id); + if (order == null) return TypedResults.NotFound(); + + order.Delivered = true; + await repository.Update(order); + + return TypedResults.NoContent(); + } } } diff --git a/exercise.pizzashopapi/Enums/OrderStatus.cs b/exercise.pizzashopapi/Enums/OrderStatus.cs new file mode 100644 index 0000000..4087e73 --- /dev/null +++ b/exercise.pizzashopapi/Enums/OrderStatus.cs @@ -0,0 +1,9 @@ +namespace exercise.pizzashopapi.Enums; + +public enum OrderStatus +{ + Preparing, + Cooking, + Delivering, + Delivered +} \ No newline at end of file diff --git a/exercise.pizzashopapi/Models/Order.cs b/exercise.pizzashopapi/Models/Order.cs index 7690753..0360611 100644 --- a/exercise.pizzashopapi/Models/Order.cs +++ b/exercise.pizzashopapi/Models/Order.cs @@ -1,4 +1,7 @@ -namespace exercise.pizzashopapi.Models +using System.ComponentModel.DataAnnotations.Schema; +using exercise.pizzashopapi.Enums; + +namespace exercise.pizzashopapi.Models { public class Order { @@ -10,7 +13,25 @@ public class Order //public List? ToppingIds { get; set; } public List? Toppings { get; set; } public DateTime OrderDate { get; set; } - + public bool Delivered { get; set; } + [NotMapped] + public OrderStatus Status + { + get + { + if (Delivered) + { + return OrderStatus.Delivered; + } + return (DateTime.UtcNow - OrderDate) switch + { + var d when d.TotalMinutes < 3 => OrderStatus.Preparing, + var d when d.TotalMinutes < 12 => OrderStatus.Cooking, + _ => OrderStatus.Delivering + }; + } + } + public Order() { OrderDate = DateTime.UtcNow;