fixes and tweaks
This commit is contained in:
@@ -5,12 +5,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace Lutra.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a dedicated endpoint group for Supermarkt-related operations.
|
||||
/// Provides endpoints for Supermarkt-related operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This controller is intentionally empty for now. Endpoints will be added when the Supermarkt
|
||||
/// use cases are implemented.
|
||||
/// </remarks>
|
||||
[ApiController]
|
||||
[Route("api/supermarkten")]
|
||||
[Produces("application/json")]
|
||||
@@ -21,7 +17,7 @@ public class SupermarktenController(IMediator mediator) : ControllerBase
|
||||
/// </summary>
|
||||
/// <param name="skip">The number of items to skip.</param>
|
||||
/// <param name="take">The maximum number of items to return.</param>
|
||||
/// <returns>The requested verspakket page.</returns>
|
||||
/// <returns>The requested supermarkt page.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(GetSupermarkten.Response), StatusCodes.Status200OK)]
|
||||
public async Task<GetSupermarkten.Response> Get(int skip = 0, int take = 50)
|
||||
|
||||
@@ -3,134 +3,158 @@ using Lutra.API.Requests;
|
||||
using Lutra.Application.Verspakketten;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Lutra.API.Controllers
|
||||
namespace Lutra.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to verspakket resources.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/verspakketten")]
|
||||
[Produces("application/json")]
|
||||
public class VerspakkettenController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to verspakket resources.
|
||||
/// Gets a page of verspakketten.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/verspakketten")]
|
||||
[Produces("application/json")]
|
||||
public class VerspakkettenController(IMediator mediator) : ControllerBase
|
||||
/// <param name="skip">The number of items to skip. Default: 0.</param>
|
||||
/// <param name="take">The maximum number of items to return. Default: 50.</param>
|
||||
/// <param name="sortField">The field to sort by: Naam, PrijsInCenten, AverageCijferSmaak, or AverageCijferBereiden. Default: Naam.</param>
|
||||
/// <param name="sortDirection">The sort direction: Ascending or Descending. Default: Ascending.</param>
|
||||
/// <returns>The requested verspakket page.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(GetVerspakketten.Response), StatusCodes.Status200OK)]
|
||||
public async Task<GetVerspakketten.Response> Get(
|
||||
int skip = 0,
|
||||
int take = 50,
|
||||
VerspakketSortField sortField = VerspakketSortField.Naam,
|
||||
SortDirection sortDirection = SortDirection.Ascending)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a page of verspakketten.
|
||||
/// </summary>
|
||||
/// <param name="skip">The number of items to skip. Default: 0.</param>
|
||||
/// <param name="take">The maximum number of items to return. Default: 50.</param>
|
||||
/// <param name="sortField">The field to sort by: Naam, PrijsInCenten, AverageCijferSmaak, or AverageCijferBereiden. Default: Naam.</param>
|
||||
/// <param name="sortDirection">The sort direction: Ascending or Descending. Default: Ascending.</param>
|
||||
/// <returns>The requested verspakket page.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(GetVerspakketten.Response), StatusCodes.Status200OK)]
|
||||
public async Task<GetVerspakketten.Response> Get(
|
||||
int skip = 0,
|
||||
int take = 50,
|
||||
VerspakketSortField sortField = VerspakketSortField.Naam,
|
||||
SortDirection sortDirection = SortDirection.Ascending)
|
||||
return await mediator.SendQueryAsync<GetVerspakketten.Query, GetVerspakketten.Response>(
|
||||
new GetVerspakketten.Query(skip, take, sortField, sortDirection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific verspakket by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket ID.</param>
|
||||
/// <returns>Returns 200 OK with the verspakket when found, or 404 Not Found when not found.</returns>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(GetVerspakket.Response), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<GetVerspakket.Response?>> GetById(Guid id)
|
||||
{
|
||||
var result = await mediator.SendQueryAsync<GetVerspakket.Query, GetVerspakket.Response?>(new GetVerspakket.Query(id));
|
||||
if (result?.Verspakket == null)
|
||||
{
|
||||
return await mediator.SendQueryAsync<GetVerspakketten.Query, GetVerspakketten.Response>(
|
||||
new GetVerspakketten.Query(skip, take, sortField, sortDirection));
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific verspakket by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket ID.</param>
|
||||
/// <returns>Returns 200 OK with the verspakket when found, or 404 Not Found when not found.</returns>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(GetVerspakket.Response), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<GetVerspakket.Response?>> GetById(Guid id)
|
||||
{
|
||||
var result = await mediator.SendQueryAsync<GetVerspakket.Query, GetVerspakket.Response?>(new GetVerspakket.Query(id));
|
||||
if (result?.Verspakket == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
/// <summary>
|
||||
/// Creates a new verspakket.
|
||||
/// </summary>
|
||||
/// <param name="request">The verspakket values to create.</param>
|
||||
/// <returns>Returns 201 Created with the created verspakket identifier.</returns>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(CreateVerspakket.Response), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<CreateVerspakket.Response>> Post([FromBody] CreateVerspakketRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var beoordeling = request.Beoordeling is null
|
||||
? null
|
||||
: new Application.Models.Verspakketten.Beoordeling
|
||||
{
|
||||
CijferSmaak = request.Beoordeling.CijferSmaak,
|
||||
CijferBereiden = request.Beoordeling.CijferBereiden,
|
||||
Aanbevolen = request.Beoordeling.Aanbevolen,
|
||||
Tekst = request.Beoordeling.Tekst
|
||||
};
|
||||
|
||||
var fotos = request.Fotos?
|
||||
.Select(f => new Application.Models.Verspakketten.VerspakketFoto(f.Base64Data, f.IsMainImage))
|
||||
.ToList();
|
||||
|
||||
var command = new CreateVerspakket.Command(
|
||||
request.Naam,
|
||||
request.PrijsInCenten,
|
||||
request.AantalPersonen,
|
||||
request.SupermarktId,
|
||||
beoordeling,
|
||||
fotos);
|
||||
|
||||
var result = await mediator.SendCommandAsync<CreateVerspakket.Command, CreateVerspakket.Response>(command);
|
||||
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new verspakket.
|
||||
/// </summary>
|
||||
/// <param name="command">The verspakket values to create.</param>
|
||||
/// <returns>Returns 201 Created with the created verspakket identifier.</returns>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(CreateVerspakket.Response), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<CreateVerspakket.Response>> Post([FromBody] CreateVerspakket.Command command)
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await mediator.SendCommandAsync<CreateVerspakket.Command, CreateVerspakket.Response>(command);
|
||||
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing verspakket with the provided values.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket identifier.</param>
|
||||
/// <param name="request">The updated verspakket values.</param>
|
||||
/// <returns>
|
||||
/// Returns 204 No Content when the update succeeds.
|
||||
/// Returns 404 Not Found when the specified verspakket does not exist.
|
||||
/// </returns>
|
||||
[HttpPut("{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateVerspakketRequest request)
|
||||
/// <summary>
|
||||
/// Updates an existing verspakket with the provided values.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket identifier.</param>
|
||||
/// <param name="request">The updated verspakket values.</param>
|
||||
/// <returns>
|
||||
/// Returns 204 No Content when the update succeeds.
|
||||
/// Returns 404 Not Found when the specified verspakket does not exist.
|
||||
/// </returns>
|
||||
[HttpPut("{id:guid}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateVerspakketRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = new UpdateVerspakket.Command(id, request.Naam, request.PrijsInCenten, request.AantalPersonen, request.SupermarktId);
|
||||
await mediator.SendCommandAsync<UpdateVerspakket.Command, UpdateVerspakket.Response>(command);
|
||||
return NoContent();
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.StartsWith($"Verspakket with id '{id}'"))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
var fotos = request.Fotos?
|
||||
.Select(f => new Application.Models.Verspakketten.VerspakketFoto(f.Base64Data, f.IsMainImage))
|
||||
.ToList();
|
||||
var command = new UpdateVerspakket.Command(id, request.Naam, request.PrijsInCenten, request.AantalPersonen, request.SupermarktId, fotos);
|
||||
await mediator.SendCommandAsync<UpdateVerspakket.Command, UpdateVerspakket.Response>(command);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a beoordeling to an existing verspakket.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket ID.</param>
|
||||
/// <param name="request">The beoordeling values to add.</param>
|
||||
/// <returns>Returns 201 Created with the created beoordeling identifier.</returns>
|
||||
[HttpPost("{id:guid}/beoordelingen")]
|
||||
[ProducesResponseType(typeof(AddBeoordeling.Response), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<AddBeoordeling.Response>> AddBeoordeling(Guid id, [FromBody] AddBeoordelingRequest request)
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = new AddBeoordeling.Command(id, request.CijferSmaak, request.CijferBereiden, request.Aanbevolen, request.Tekst);
|
||||
var result = await mediator.SendCommandAsync<AddBeoordeling.Command, AddBeoordeling.Response>(command);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.StartsWith($"Verspakket with id '{id}'"))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return CreatedAtAction(nameof(GetById), new { id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a beoordeling to an existing verspakket.
|
||||
/// </summary>
|
||||
/// <param name="id">The verspakket ID.</param>
|
||||
/// <param name="request">The beoordeling values to add.</param>
|
||||
/// <returns>Returns 201 Created with the created beoordeling identifier.</returns>
|
||||
[HttpPost("{id:guid}/beoordelingen")]
|
||||
[ProducesResponseType(typeof(AddBeoordeling.Response), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<AddBeoordeling.Response>> AddBeoordeling(Guid id, [FromBody] AddBeoordelingRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = new AddBeoordeling.Command(id, request.CijferSmaak, request.CijferBereiden, request.Aanbevolen, request.Tekst);
|
||||
var result = await mediator.SendCommandAsync<AddBeoordeling.Command, AddBeoordeling.Response>(command);
|
||||
|
||||
return CreatedAtAction(nameof(GetById), new { id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
# This stage is used when running from VS in fast mode (Default for Debug configuration)
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
@@ -9,7 +9,7 @@ EXPOSE 8081
|
||||
|
||||
|
||||
# This stage is used to build the service project
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["Lutra.API/Lutra.API.csproj", "Lutra.API/"]
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cortex.Mediator" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.6">
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.14.0" />
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.14.4" />
|
||||
<ProjectReference Include="..\Lutra.Infrastructure\Lutra.Infrastructure.Sql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Lutra.API.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the data required to create a verspakket.
|
||||
/// </summary>
|
||||
public sealed record CreateVerspakketRequest(
|
||||
[Required, MaxLength(50)] string Naam,
|
||||
[Range(0, int.MaxValue)] int? PrijsInCenten,
|
||||
[Range(1, 10)] int AantalPersonen,
|
||||
[Required] Guid SupermarktId,
|
||||
AddBeoordelingRequest? Beoordeling = null,
|
||||
IReadOnlyList<VerspakketFotoRequest>? Fotos = null);
|
||||
@@ -9,4 +9,7 @@ public sealed record UpdateVerspakketRequest(
|
||||
[Required, MaxLength(50)] string Naam,
|
||||
[Range(0, int.MaxValue)] int PrijsInCenten,
|
||||
[Range(1, 10)] int AantalPersonen,
|
||||
[Required] Guid SupermarktId);
|
||||
[Required] Guid SupermarktId,
|
||||
IReadOnlyList<VerspakketFotoRequest>? Fotos = null);
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Lutra.API.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a foto in a request, encoded as base64.
|
||||
/// </summary>
|
||||
public sealed record VerspakketFotoRequest(
|
||||
[Required] string Base64Data,
|
||||
bool IsMainImage);
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"LutraDb": "Host=localhost;Database=lutra;Username=postgres;Password=<set-locally>"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"LutraDb": "Host=db.m91.nl;Username=user;Password=password;Database=Lutra"
|
||||
"LutraDb": ""
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user