diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommand.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommand.cs index 7ae2079f8..eef9ce48a 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommand.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommand.cs @@ -12,12 +12,12 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; public sealed class PurgeDialogCommand : IRequest { - public Guid Id { get; set; } + public Guid DialogId { get; set; } public Guid? IfMatchDialogRevision { get; set; } } [GenerateOneOf] -public partial class PurgeDialogResult : OneOfBase; +public partial class PurgeDialogResult : OneOfBase; internal sealed class PurgeDialogCommandHandler : IRequestHandler { @@ -43,11 +43,11 @@ public async Task Handle(PurgeDialogCommand request, Cancella .Include(x => x.Elements) .Include(x => x.Activities) .Where(x => resourceIds.Contains(x.ServiceResource)) - .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken); + .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken); if (dialog is null) { - return new EntityNotFound(request.Id); + return new EntityNotFound(request.DialogId); } _db.Dialogs.HardRemove(dialog); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommandValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommandValidator.cs new file mode 100644 index 000000000..6c53c3cac --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommandValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; + +internal sealed class PurgeDialogCommandValidator : AbstractValidator +{ + public PurgeDialogCommandValidator() + { + RuleFor(x => x.DialogId) + .NotEqual(default(Guid)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/PurgeDialogEndpoint.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/PurgeDialogEndpoint.cs index 1a6baa7ca..31c1e0b22 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/PurgeDialogEndpoint.cs +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/PurgeDialogEndpoint.cs @@ -19,11 +19,13 @@ public PurgeDialogEndpoint(ISender sender) public override void Configure() { Post("dialogs/{dialogId}/actions/purge"); + RequestBinder(new PurgeDialogRequestBinder()); Policies(AuthorizationPolicy.ServiceProvider); Group(); Description(b => b .OperationId("PurgeDialog") + .Accepts() .ProducesOneOf( StatusCodes.Status204NoContent, StatusCodes.Status404NotFound, @@ -33,21 +35,22 @@ public override void Configure() public override async Task HandleAsync(PurgeDialogRequest req, CancellationToken ct) { - var command = new PurgeDialogCommand { Id = req.DialogId, IfMatchDialogRevision = req.IfMatchDialogRevision }; + var command = new PurgeDialogCommand { DialogId = req.DialogId, IfMatchDialogRevision = req.IfMatchDialogRevision }; var result = await _sender.Send(command, ct); await result.Match( success => SendNoContentAsync(ct), notFound => this.NotFoundAsync(notFound, ct), - concurrencyError => this.PreconditionFailed(ct)); + concurrencyError => this.PreconditionFailed(ct), + validationError => this.BadRequestAsync(validationError, ct)); } } public sealed class PurgeDialogRequest { - public Guid DialogId { get; set; } + public Guid DialogId { get; init; } [FromHeader(headerName: Constants.IfMatch, isRequired: false, removeFromSchema: true)] - public Guid? IfMatchDialogRevision { get; set; } + public Guid? IfMatchDialogRevision { get; init; } } public sealed class PurgeDialogEndpointSummary : Summary @@ -67,3 +70,22 @@ Deletes a given dialog (hard delete). For more information see the documentation Responses[StatusCodes.Status412PreconditionFailed] = Constants.SwaggerSummary.RevisionMismatch; } } + +// Custom request binder to avoid attempted automatic deserialization of the Request body if the content type is application/json +public class PurgeDialogRequestBinder : IRequestBinder +{ + public ValueTask BindAsync(BinderContext ctx, CancellationToken ct) + { + if (!Guid.TryParse(ctx.HttpContext.Request.RouteValues["dialogId"]?.ToString()!, out var dialogId)) + return ValueTask.FromResult(new PurgeDialogRequest()); + + ctx.HttpContext.Request.Headers.TryGetValue(Constants.IfMatch, out var revisionHeader); + var revisionFound = Guid.TryParse(revisionHeader, out var revision); + + return ValueTask.FromResult(new PurgeDialogRequest + { + DialogId = dialogId, + IfMatchDialogRevision = revisionFound ? revision : null + }); + } +} diff --git a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs index 6fe196918..758e0d548 100644 --- a/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs +++ b/src/Digdir.Library.Entity.EntityFrameworkCore/Features/Aggregate/AggregateExtensions.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Reflection; using Digdir.Library.Entity.Abstractions.Features.Aggregate; -using Digdir.Library.Entity.Abstractions.Features.SoftDeletable; using Digdir.Library.Entity.Abstractions.Features.Updatable; using Digdir.Library.Entity.Abstractions.Features.Versionable; using Digdir.Library.Entity.EntityFrameworkCore.Features.SoftDeletable; diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Common/Events/DomainEventsTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Common/Events/DomainEventsTests.cs index b0fcc60cc..424c72985 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Common/Events/DomainEventsTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Common/Events/DomainEventsTests.cs @@ -242,7 +242,7 @@ public async Task Creates_DialogDeletedEvent_When_Dialog_Purged() // Act var purgeCommand = new PurgeDialogCommand { - Id = dialogId + DialogId = dialogId }; await Application.Send(purgeCommand); @@ -273,7 +273,7 @@ public async Task Creates_DialogElementDeleted_CloudEvent_When_Purging_Dialog() // Act var purgeCommand = new PurgeDialogCommand { - Id = dialogId + DialogId = dialogId }; await Application.Send(purgeCommand); diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/PurgeDialogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/PurgeDialogTests.cs index e59a20876..98f361b7c 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/PurgeDialogTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/PurgeDialogTests.cs @@ -21,7 +21,7 @@ public async Task Purge_RemovesDialog_FromDatabase() createResponse.TryPickT0(out _, out _).Should().BeTrue(); // Act - var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId }; + var purgeCommand = new PurgeDialogCommand { DialogId = expectedDialogId }; var purgeResponse = await Application.Send(purgeCommand); // Assert @@ -47,7 +47,7 @@ public async Task Purge_ReturnsConcurrencyError_OnIfMatchDialogRevisionMismatch( createResponse.TryPickT0(out _, out _).Should().BeTrue(); // Act - var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId, IfMatchDialogRevision = Guid.NewGuid() }; + var purgeCommand = new PurgeDialogCommand { DialogId = expectedDialogId, IfMatchDialogRevision = Guid.NewGuid() }; var purgeResponse = await Application.Send(purgeCommand); // Assert @@ -61,7 +61,7 @@ public async Task Purge_ReturnsNotFound_OnNonExistingDialog() var expectedDialogId = Guid.NewGuid(); var createCommand = DialogGenerator.GenerateFakeDialog(id: expectedDialogId); await Application.Send(createCommand); - var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId }; + var purgeCommand = new PurgeDialogCommand { DialogId = expectedDialogId }; await Application.Send(purgeCommand); // Act