From a10bcfa1d6a13df25b66be8b0988ad72a04d6288 Mon Sep 17 00:00:00 2001 From: moarten Date: Fri, 17 Apr 2026 22:02:25 +0200 Subject: [PATCH] Endpoint to add a verspakket --- .../Controllers/VerspakkettenController.cs | 24 ++++++++++- Lutra/Lutra.API/Lutra.API.csproj | 8 ++-- .../Lutra.Application.csproj | 2 +- .../Models/Supermarkten/Supermarkt.cs | 2 + .../Models/Verspakketten/Beoordeling.cs | 3 ++ .../Models/Verspakketten/Verspakket.cs | 5 +++ .../Supermarkten/GetSupermarkten.Handler.cs | 7 +++- .../Verspakketten/CreateVerspakket.Command.cs | 12 ++++++ .../Verspakketten/CreateVerspakket.Handler.cs | 40 +++++++++++++++++++ .../CreateVerspakket.Response.cs | 9 +++++ .../Verspakketten/CreateVerspakket.cs | 3 ++ .../Verspakketten/GetVerspakket.Handler.cs | 2 + .../Verspakketten/GetVerspakketten.Handler.cs | 6 ++- Lutra/Lutra.Domain/Entities/BaseEntity.cs | 2 - Lutra/Lutra.Domain/Entities/Verspakket.cs | 2 +- .../Lutra.Infrastructure.Migrator.csproj | 4 +- .../Lutra.Infrastructure.Sql.csproj | 6 +-- 17 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Command.cs create mode 100644 Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Handler.cs create mode 100644 Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Response.cs create mode 100644 Lutra/Lutra.Application/Verspakketten/CreateVerspakket.cs diff --git a/Lutra/Lutra.API/Controllers/VerspakkettenController.cs b/Lutra/Lutra.API/Controllers/VerspakkettenController.cs index e0d39a5..37a2a52 100644 --- a/Lutra/Lutra.API/Controllers/VerspakkettenController.cs +++ b/Lutra/Lutra.API/Controllers/VerspakkettenController.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; namespace Lutra.API.Controllers { /// - /// Provides read-only access to verspakket resources. + /// Provides access to verspakket resources. /// [ApiController] [Route("api/verspakketten")] @@ -50,5 +50,27 @@ namespace Lutra.API.Controllers return Ok(result); } + + /// + /// Creates a new verspakket. + /// + /// The verspakket values to create. + /// Returns 201 Created with the created verspakket identifier. + [HttpPost] + [ProducesResponseType(typeof(CreateVerspakket.Response), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Post([FromBody] CreateVerspakket.Command command) + { + try + { + var result = await mediator.SendCommandAsync(command); + + return CreatedAtAction(nameof(GetById), new { id = result.Id }, result); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } } } \ No newline at end of file diff --git a/Lutra/Lutra.API/Lutra.API.csproj b/Lutra/Lutra.API/Lutra.API.csproj index bf52713..4441b7e 100644 --- a/Lutra/Lutra.API/Lutra.API.csproj +++ b/Lutra/Lutra.API/Lutra.API.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -12,13 +12,13 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/Lutra/Lutra.Application/Lutra.Application.csproj b/Lutra/Lutra.Application/Lutra.Application.csproj index c836440..109f6fd 100644 --- a/Lutra/Lutra.Application/Lutra.Application.csproj +++ b/Lutra/Lutra.Application/Lutra.Application.csproj @@ -8,7 +8,7 @@ - + diff --git a/Lutra/Lutra.Application/Models/Supermarkten/Supermarkt.cs b/Lutra/Lutra.Application/Models/Supermarkten/Supermarkt.cs index 6f6ad50..31c2398 100644 --- a/Lutra/Lutra.Application/Models/Supermarkten/Supermarkt.cs +++ b/Lutra/Lutra.Application/Models/Supermarkten/Supermarkt.cs @@ -2,6 +2,8 @@ { public record Supermarkt { + public required Guid Id { get; init; } + public required string Naam { get; init; } } } diff --git a/Lutra/Lutra.Application/Models/Verspakketten/Beoordeling.cs b/Lutra/Lutra.Application/Models/Verspakketten/Beoordeling.cs index cb3bc0d..45fb971 100644 --- a/Lutra/Lutra.Application/Models/Verspakketten/Beoordeling.cs +++ b/Lutra/Lutra.Application/Models/Verspakketten/Beoordeling.cs @@ -3,7 +3,10 @@ public class Beoordeling { public required int CijferSmaak { get; init; } + public required int CijferBereiden { get; init; } + public required bool Aanbevolen { get; init; } + public string? Tekst { get; init; } } diff --git a/Lutra/Lutra.Application/Models/Verspakketten/Verspakket.cs b/Lutra/Lutra.Application/Models/Verspakketten/Verspakket.cs index ea86fd8..46a7d78 100644 --- a/Lutra/Lutra.Application/Models/Verspakketten/Verspakket.cs +++ b/Lutra/Lutra.Application/Models/Verspakketten/Verspakket.cs @@ -4,9 +4,14 @@ namespace Lutra.Application.Models.Verspakketten { public record Verspakket { + public required Guid Id { get; init; } + public required string Naam { get; init; } + public int? PrijsInCenten { get; init; } + public Beoordeling[]? Beoordelingen { get; init; } + public Supermarkt? Supermarkt { get; init; } } } diff --git a/Lutra/Lutra.Application/Supermarkten/GetSupermarkten.Handler.cs b/Lutra/Lutra.Application/Supermarkten/GetSupermarkten.Handler.cs index 303c02e..756e766 100644 --- a/Lutra/Lutra.Application/Supermarkten/GetSupermarkten.Handler.cs +++ b/Lutra/Lutra.Application/Supermarkten/GetSupermarkten.Handler.cs @@ -13,10 +13,15 @@ namespace Lutra.Application.Supermarkten { var supermarkten = await context.Supermarkten .AsNoTracking() + .Where(w => w.DeletedAt == null) .OrderBy(s => s.Naam) .Skip(request.Skip) .Take(request.Take) - .Select(s => new Supermarkt { Naam = s.Naam }) + .Select(s => new Supermarkt + { + Id = s.Id, + Naam = s.Naam + }) .ToListAsync(cancellationToken); return new Response { Supermarkten = supermarkten }; diff --git a/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Command.cs b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Command.cs new file mode 100644 index 0000000..9a35b7c --- /dev/null +++ b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Command.cs @@ -0,0 +1,12 @@ +using Cortex.Mediator.Commands; + +namespace Lutra.Application.Verspakketten; + +public sealed partial class CreateVerspakket +{ + public sealed record Command( + string Naam, + int? PrijsInCenten, + int AantalPersonen, + Guid SupermarktId) : ICommand; +} diff --git a/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Handler.cs b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Handler.cs new file mode 100644 index 0000000..271f3b1 --- /dev/null +++ b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Handler.cs @@ -0,0 +1,40 @@ +using Cortex.Mediator.Commands; +using Lutra.Application.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Lutra.Application.Verspakketten; + +public sealed partial class CreateVerspakket +{ + public sealed class Handler(ILutraDbContext context) : ICommandHandler + { + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var supermarktExists = await context.Supermarkten + .AsNoTracking() + .AnyAsync(s => s.Id == request.SupermarktId, cancellationToken); + + if (!supermarktExists) + { + throw new InvalidOperationException($"Supermarkt with id '{request.SupermarktId}' was not found."); + } + + var now = DateTime.UtcNow; + var verspakket = new Domain.Entities.Verspakket + { + Id = Guid.NewGuid(), + Naam = request.Naam, + PrijsInCenten = request.PrijsInCenten, + AantalPersonen = request.AantalPersonen, + SupermarktId = request.SupermarktId, + CreatedAt = now, + ModifiedAt = now + }; + + await context.Verspaketten.AddAsync(verspakket, cancellationToken); + await context.SaveChangesAsync(cancellationToken); + + return new Response { Id = verspakket.Id }; + } + } +} diff --git a/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Response.cs b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Response.cs new file mode 100644 index 0000000..e2c3f91 --- /dev/null +++ b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.Response.cs @@ -0,0 +1,9 @@ +namespace Lutra.Application.Verspakketten; + +public sealed partial class CreateVerspakket +{ + public sealed class Response + { + public required Guid Id { get; set; } + } +} diff --git a/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.cs b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.cs new file mode 100644 index 0000000..9be41ca --- /dev/null +++ b/Lutra/Lutra.Application/Verspakketten/CreateVerspakket.cs @@ -0,0 +1,3 @@ +namespace Lutra.Application.Verspakketten; + +public sealed partial class CreateVerspakket { } diff --git a/Lutra/Lutra.Application/Verspakketten/GetVerspakket.Handler.cs b/Lutra/Lutra.Application/Verspakketten/GetVerspakket.Handler.cs index fb0f059..2e0b010 100644 --- a/Lutra/Lutra.Application/Verspakketten/GetVerspakket.Handler.cs +++ b/Lutra/Lutra.Application/Verspakketten/GetVerspakket.Handler.cs @@ -17,6 +17,7 @@ namespace Lutra.Application.Verspakketten .Where(v => v.Id == request.Id) .Select(v => new Verspakket { + Id = v.Id, Naam = v.Naam, PrijsInCenten = v.PrijsInCenten, Beoordelingen = v.Beoordelingen @@ -30,6 +31,7 @@ namespace Lutra.Application.Verspakketten .ToArray(), Supermarkt = new Supermarkt { + Id = v.Supermarkt.Id, Naam = v.Supermarkt.Naam } }) diff --git a/Lutra/Lutra.Application/Verspakketten/GetVerspakketten.Handler.cs b/Lutra/Lutra.Application/Verspakketten/GetVerspakketten.Handler.cs index d04ded3..1f073f9 100644 --- a/Lutra/Lutra.Application/Verspakketten/GetVerspakketten.Handler.cs +++ b/Lutra/Lutra.Application/Verspakketten/GetVerspakketten.Handler.cs @@ -12,7 +12,9 @@ namespace Lutra.Application.Verspakketten { public async Task Handle(Query request, CancellationToken cancellationToken) { - var query = context.Verspaketten.AsNoTracking(); + var query = context.Verspaketten + .Where(w => w.DeletedAt == null) + .AsNoTracking(); // Apply sort before pagination so the database handles ordering efficiently. IOrderedQueryable sorted = request.SortField switch @@ -40,6 +42,7 @@ namespace Lutra.Application.Verspakketten .Take(request.Take) .Select(v => new Verspakket { + Id = v.Id, Naam = v.Naam, PrijsInCenten = v.PrijsInCenten, Beoordelingen = v.Beoordelingen @@ -53,6 +56,7 @@ namespace Lutra.Application.Verspakketten .ToArray(), Supermarkt = new Supermarkt { + Id = v.Supermarkt.Id, Naam = v.Supermarkt.Naam } }) diff --git a/Lutra/Lutra.Domain/Entities/BaseEntity.cs b/Lutra/Lutra.Domain/Entities/BaseEntity.cs index 222e25c..60a7d0d 100644 --- a/Lutra/Lutra.Domain/Entities/BaseEntity.cs +++ b/Lutra/Lutra.Domain/Entities/BaseEntity.cs @@ -9,6 +9,4 @@ public abstract class BaseEntity public DateTime ModifiedAt { get; set; } public DateTime? DeletedAt { get; set; } - - public bool IsDeleted => DeletedAt.HasValue; } diff --git a/Lutra/Lutra.Domain/Entities/Verspakket.cs b/Lutra/Lutra.Domain/Entities/Verspakket.cs index 2d6bb95..d31a98e 100644 --- a/Lutra/Lutra.Domain/Entities/Verspakket.cs +++ b/Lutra/Lutra.Domain/Entities/Verspakket.cs @@ -16,7 +16,7 @@ public class Verspakket : BaseEntity public required Guid SupermarktId { get; set; } - public required virtual Supermarkt Supermarkt { get; set; } + public virtual Supermarkt Supermarkt { get; set; } = null!; public IReadOnlyCollection Beoordelingen => _beoordelingen.AsReadOnly(); diff --git a/Lutra/Lutra.Infrastructure.Migrator/Lutra.Infrastructure.Migrator.csproj b/Lutra/Lutra.Infrastructure.Migrator/Lutra.Infrastructure.Migrator.csproj index 44bd8d9..7c181e2 100644 --- a/Lutra/Lutra.Infrastructure.Migrator/Lutra.Infrastructure.Migrator.csproj +++ b/Lutra/Lutra.Infrastructure.Migrator/Lutra.Infrastructure.Migrator.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/Lutra/Lutra.Infrastructure/Lutra.Infrastructure.Sql.csproj b/Lutra/Lutra.Infrastructure/Lutra.Infrastructure.Sql.csproj index eb0075d..c1bfb82 100644 --- a/Lutra/Lutra.Infrastructure/Lutra.Infrastructure.Sql.csproj +++ b/Lutra/Lutra.Infrastructure/Lutra.Infrastructure.Sql.csproj @@ -7,12 +7,12 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - +