using System.Security.Claims; using ItWorx.KnowledgeHub.Api.Authorization; using ItWorx.KnowledgeHub.Api.Security; using ItWorx.KnowledgeHub.Application.Migration; using ItWorx.KnowledgeHub.Application.Migration.UseCases; using ItWorx.KnowledgeHub.Contracts.Errors; using ItWorx.KnowledgeHub.Contracts.Migration; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; namespace ItWorx.KnowledgeHub.Api.Endpoints; /// /// Explicit administrative purge for an externally published source batch. It accepts /// only a reviewed list of target page identifiers and refuses ambiguous scope before /// it can mutate production data. /// public static class SourceBatchPurgeEndpoints { public static IEndpointRouteBuilder MapSourceBatchPurgeEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/v1/admin/source-batches/purge") .WithTags("Source batch purge") .RequireAuthorization(AuthorizationPolicies.RequireAdministrator); group.MapPost("/dry-run", DryRunAsync).AddEndpointFilter(); group.MapPost("/execute", ExecuteAsync).AddEndpointFilter(); return app; } private static async Task> DryRunAsync( [FromBody] SourceBatchPurgeRequest request, PurgeSourceBatchUseCase useCase, CancellationToken cancellationToken) => TypedResults.Ok(ToResponse(await useCase.DryRunAsync(request.BatchId, request.Source, request.Pages, cancellationToken))); private static async Task, ProblemHttpResult>> ExecuteAsync( [FromBody] ExecuteSourceBatchPurgeRequest request, PurgeSourceBatchUseCase useCase, ClaimsPrincipal principal, HttpContext httpContext, CancellationToken cancellationToken) { var correlationId = httpContext.Items[CorrelationIdMiddleware.ItemKey] as string; var result = await useCase.ExecuteAsync( new PurgeSourceBatchCommand( request.BatchId, request.Source, request.Pages, request.ConfirmationHash, Guid.Parse(principal.FindFirstValue(ClaimTypes.NameIdentifier)!), correlationId), cancellationToken); if (!result.Succeeded) { return TypedResults.Problem( statusCode: StatusCodes.Status409Conflict, type: ProblemTypes.Validation, title: result.StaleDryRun ? "Dry-run is niet langer actueel." : "Bronbatch voldoet niet aan de veiligheidscontroles.", detail: result.StaleDryRun ? "Voer opnieuw een dry-run uit; de geselecteerde gegevens zijn intussen gewijzigd." : string.Join(" ", result.Inspection.Blockers)); } return TypedResults.Ok(new SourceBatchPurgeResponse( "purged", ToResponse(result.Inspection), result.RemovedPages, result.RemovedRevisions, result.RemovedDrafts, result.RemovedLinks, result.RemovedAttachmentUsages, result.RemovedAttachmentReferences, result.RemovedImportJobs, result.RemovedImportItems, result.RemovedProvenanceRecords, result.RemovedBlobs)); } private static SourceBatchPurgeDryRunResponse ToResponse(SourceBatchPurgeInspection value) => new( value.IsSafe, value.ConfirmationHash, value.PageCount, value.DescendantPageCount, value.RevisionCount, value.DraftCount, value.LinkCount, value.AttachmentUsageCount, value.AttachmentReferenceCount, value.OrphanedBlobCount, value.SharedBlobCount, value.ImportJobCount, value.ImportItemCount, value.ProvenanceCount, value.SourceTagAssignmentCount, value.SearchIndexRecordCount, value.GeneratedSourceDerivedPageCount, value.RetainedPageCount, value.Blockers); }