-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add purge functionallity separate from soft delete. (#483)
Co-authored-by: Ole Jørgen Skogstad <skogstad@softis.net>
- Loading branch information
1 parent
e330ce3
commit 1349efb
Showing
11 changed files
with
280 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 1 addition & 2 deletions
3
...Digdir.Domain.Dialogporten.Application/Features/V1/Common/Localizations/MappingProfile.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...gporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System.Diagnostics; | ||
using Digdir.Domain.Dialogporten.Application.Common; | ||
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; | ||
using Digdir.Domain.Dialogporten.Application.Externals; | ||
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; | ||
using Digdir.Library.Entity.EntityFrameworkCore.Features.SoftDeletable; | ||
using MediatR; | ||
using Microsoft.EntityFrameworkCore; | ||
using OneOf; | ||
using OneOf.Types; | ||
|
||
namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; | ||
public sealed class PurgeDialogCommand : IRequest<PurgeDialogResult> | ||
{ | ||
public Guid Id { get; set; } | ||
public Guid? IfMatchDialogRevision { get; set; } | ||
} | ||
|
||
[GenerateOneOf] | ||
public partial class PurgeDialogResult : OneOfBase<Success, EntityNotFound, ConcurrencyError>; | ||
|
||
internal sealed class PurgeDialogCommandHandler : IRequestHandler<PurgeDialogCommand, PurgeDialogResult> | ||
{ | ||
private readonly IDialogDbContext _db; | ||
private readonly IUnitOfWork _unitOfWork; | ||
private readonly IUserResourceRegistry _userResourceRegistry; | ||
|
||
public PurgeDialogCommandHandler( | ||
IDialogDbContext db, | ||
IUnitOfWork unitOfWork, | ||
IUserResourceRegistry userResourceRegistry) | ||
{ | ||
_db = db ?? throw new ArgumentNullException(nameof(db)); | ||
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); | ||
_userResourceRegistry = userResourceRegistry ?? throw new ArgumentNullException(nameof(userResourceRegistry)); | ||
} | ||
|
||
public async Task<PurgeDialogResult> Handle(PurgeDialogCommand request, CancellationToken cancellationToken) | ||
{ | ||
var resourceIds = await _userResourceRegistry.GetCurrentUserResourceIds(cancellationToken); | ||
|
||
var dialog = await _db.Dialogs | ||
.Include(x => x.Elements) | ||
.Include(x => x.Activities) | ||
.Where(x => resourceIds.Contains(x.ServiceResource)) | ||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken); | ||
|
||
if (dialog is null) | ||
{ | ||
return new EntityNotFound<DialogEntity>(request.Id); | ||
} | ||
|
||
_db.Dialogs.HardRemove(dialog); | ||
var saveResult = await _unitOfWork | ||
.EnableConcurrencyCheck(dialog, request.IfMatchDialogRevision) | ||
.SaveChangesAsync(cancellationToken); | ||
|
||
return saveResult.Match<PurgeDialogResult>( | ||
success => success, | ||
domainError => throw new UnreachableException("Should never get a domain error when deleting a dialog"), | ||
concurrencyError => concurrencyError); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
...igdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/PurgeDialogEndpoint.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; | ||
using Digdir.Domain.Dialogporten.WebApi.Common; | ||
using Digdir.Domain.Dialogporten.WebApi.Common.Authorization; | ||
using Digdir.Domain.Dialogporten.WebApi.Common.Extensions; | ||
using FastEndpoints; | ||
using MediatR; | ||
|
||
namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.ServiceOwner.Dialogs; | ||
|
||
public sealed class PurgeDialogEndpoint : Endpoint<PurgeDialogRequest> | ||
{ | ||
private readonly ISender _sender; | ||
|
||
public PurgeDialogEndpoint(ISender sender) | ||
{ | ||
_sender = sender ?? throw new ArgumentNullException(nameof(sender)); | ||
} | ||
|
||
public override void Configure() | ||
{ | ||
Post("dialogs/{dialogId}/actions/purge"); | ||
Policies(AuthorizationPolicy.ServiceProvider); | ||
Group<ServiceOwnerGroup>(); | ||
|
||
Description(b => b | ||
.OperationId("PurgeDialog") | ||
.ProducesOneOf( | ||
StatusCodes.Status204NoContent, | ||
StatusCodes.Status404NotFound, | ||
StatusCodes.Status412PreconditionFailed) | ||
); | ||
} | ||
|
||
public override async Task HandleAsync(PurgeDialogRequest req, CancellationToken ct) | ||
{ | ||
var command = new PurgeDialogCommand { Id = 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)); | ||
} | ||
} | ||
|
||
public sealed class PurgeDialogRequest | ||
{ | ||
public Guid DialogId { get; set; } | ||
|
||
[FromHeader(headerName: Constants.IfMatch, isRequired: false, removeFromSchema: true)] | ||
public Guid? IfMatchDialogRevision { get; set; } | ||
} | ||
|
||
public sealed class PurgeDialogEndpointSummary : Summary<PurgeDialogEndpoint> | ||
{ | ||
public PurgeDialogEndpointSummary() | ||
{ | ||
Summary = "Permanently deletes a dialog"; | ||
Description = """ | ||
Deletes a given dialog (hard delete). For more information see the documentation (link TBD). | ||
Optimistic concurrency control is implemented using the If-Match header. Supply the Revision value from the GetDialog endpoint to ensure that the dialog is not deleted by another request in the meantime. | ||
"""; | ||
Responses[StatusCodes.Status204NoContent] = Constants.SwaggerSummary.Deleted.FormatInvariant("aggregate"); | ||
Responses[StatusCodes.Status401Unauthorized] = Constants.SwaggerSummary.ServiceOwnerAuthenticationFailure.FormatInvariant(AuthorizationScope.ServiceProvider); | ||
Responses[StatusCodes.Status403Forbidden] = Constants.SwaggerSummary.AccessDeniedToDialog.FormatInvariant("delete"); | ||
Responses[StatusCodes.Status404NotFound] = Constants.SwaggerSummary.DialogNotFound; | ||
Responses[StatusCodes.Status412PreconditionFailed] = Constants.SwaggerSummary.RevisionMismatch; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 1 addition & 8 deletions
9
...logporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/CreateDialogTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
...alogporten.Application.Integration.Tests/Features/V1/Dialogs/Commands/PurgeDialogTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; | ||
using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; | ||
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; | ||
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; | ||
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements; | ||
using Digdir.Tool.Dialogporten.GenerateFakeData; | ||
using FluentAssertions; | ||
|
||
namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.Dialogs.Commands; | ||
|
||
[Collection(nameof(DialogCqrsCollectionFixture))] | ||
public class PurgeDialogTests(DialogApplication application) : ApplicationCollectionFixture(application) | ||
{ | ||
[Fact] | ||
public async Task Purge_RemovesDialog_FromDatabase() | ||
{ | ||
// Arrange | ||
var expectedDialogId = Guid.NewGuid(); | ||
var createCommand = DialogGenerator.GenerateFakeDialog(id: expectedDialogId); | ||
var createResponse = await Application.Send(createCommand); | ||
createResponse.TryPickT0(out _, out _).Should().BeTrue(); | ||
|
||
// Act | ||
var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId }; | ||
var purgeResponse = await Application.Send(purgeCommand); | ||
|
||
// Assert | ||
purgeResponse.TryPickT0(out _, out _).Should().BeTrue(); | ||
|
||
var dialogEntities = await Application.GetDbEntities<DialogEntity>(); | ||
dialogEntities.Should().BeEmpty(); | ||
|
||
var dialogElements = await Application.GetDbEntities<DialogElement>(); | ||
dialogElements.Should().BeEmpty(); | ||
|
||
var dialogActivities = await Application.GetDbEntities<DialogActivity>(); | ||
dialogActivities.Should().BeEmpty(); | ||
} | ||
|
||
[Fact] | ||
public async Task Purge_ReturnsConcurrencyError_OnIfMatchDialogRevisionMismatch() | ||
{ | ||
// Arrange | ||
var expectedDialogId = Guid.NewGuid(); | ||
var createCommand = DialogGenerator.GenerateFakeDialog(id: expectedDialogId); | ||
var createResponse = await Application.Send(createCommand); | ||
createResponse.TryPickT0(out _, out _).Should().BeTrue(); | ||
|
||
// Act | ||
var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId, IfMatchDialogRevision = Guid.NewGuid() }; | ||
var purgeResponse = await Application.Send(purgeCommand); | ||
|
||
// Assert | ||
purgeResponse.TryPickT2(out _, out _).Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public async Task Purge_ReturnsNotFound_OnNonExistingDialog() | ||
{ | ||
// Arrange | ||
var expectedDialogId = Guid.NewGuid(); | ||
var createCommand = DialogGenerator.GenerateFakeDialog(id: expectedDialogId); | ||
await Application.Send(createCommand); | ||
var purgeCommand = new PurgeDialogCommand { Id = expectedDialogId }; | ||
await Application.Send(purgeCommand); | ||
|
||
// Act | ||
var purgeResponse = await Application.Send(purgeCommand); | ||
|
||
// Assert | ||
purgeResponse.TryPickT1(out _, out _).Should().BeTrue(); | ||
} | ||
} |
Oops, something went wrong.