| | | 1 | | using ArturRios.Heimdall.Command.Input; |
| | | 2 | | using ArturRios.Heimdall.Command.Output; |
| | | 3 | | using ArturRios.Heimdall.Shared.Retention; |
| | | 4 | | using ArturRios.Mediator.Command; |
| | | 5 | | |
| | | 6 | | namespace ArturRios.Heimdall.WebApi.Retention; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Runs <see cref="PseudonymiseAuditActorsCommand" /> on an interval, so the audit trail stops |
| | | 10 | | /// naming people, as actors or as targets, once it no longer needs to, and drops the tombstones of |
| | | 11 | | /// hard-deleted identities once nothing names them (NFR-21). |
| | | 12 | | /// </summary> |
| | | 13 | | public class AuditRetentionService( |
| | | 14 | | IServiceScopeFactory scopeFactory, |
| | | 15 | | DataRetentionOptions retention, |
| | | 16 | | ILogger<AuditRetentionService> logger) |
| | 970 | 17 | | : ScheduledPassService(scopeFactory, retention.PurgeInterval, logger) |
| | | 18 | | { |
| | 0 | 19 | | public override string PassName => "audit-retention"; |
| | | 20 | | |
| | | 21 | | protected override string StartupDescription => |
| | 966 | 22 | | $"Audit attribution pseudonymisation scheduled every {retention.PurgeInterval}, clearing " + |
| | 966 | 23 | | $"attributions and person targets after {retention.AuditActorRetention} and immediately " + |
| | 966 | 24 | | "for erased identities"; |
| | | 25 | | |
| | | 26 | | protected override async Task<ScheduledPassResult> RunAsync(IServiceProvider scope) |
| | | 27 | | { |
| | 4 | 28 | | var mediator = scope.GetRequiredService<CommandMediator>(); |
| | | 29 | | |
| | 4 | 30 | | var result = await mediator |
| | 4 | 31 | | .ExecuteCommandAsync<PseudonymiseAuditActorsCommand, PseudonymiseAuditActorsCommandOutput>( |
| | 4 | 32 | | new PseudonymiseAuditActorsCommand()); |
| | | 33 | | |
| | 4 | 34 | | if (!result.Success) |
| | | 35 | | { |
| | 1 | 36 | | return ScheduledPassResult.Failure( |
| | 1 | 37 | | "Audit attribution pseudonymisation", result.Errors, result.Data is { } done ? Rows(done) : 0); |
| | | 38 | | } |
| | | 39 | | |
| | 3 | 40 | | var pseudonymised = result.Data!; |
| | 3 | 41 | | var rows = Rows(pseudonymised); |
| | | 42 | | |
| | | 43 | | // The rows still due are what the run left at its batch cap (#206): a number that does not fall |
| | | 44 | | // from run to run is a backlog that is not draining. |
| | 3 | 45 | | return rows == 0 && pseudonymised.Backlog == 0 |
| | 3 | 46 | | ? ScheduledPassResult.Quiet |
| | 3 | 47 | | : new ScheduledPassResult( |
| | 3 | 48 | | $"Cleared the actor attribution from {pseudonymised.EntriesPseudonymised} audit entries " + |
| | 3 | 49 | | $"and the person target from {pseudonymised.TargetsCleared}, and removed " + |
| | 3 | 50 | | $"{pseudonymised.TombstonesRemoved} tombstones of hard-deleted identities; " + |
| | 3 | 51 | | $"{pseudonymised.Backlog} still due after this run", |
| | 3 | 52 | | false, rows); |
| | 4 | 53 | | } |
| | | 54 | | |
| | | 55 | | private static long Rows(PseudonymiseAuditActorsCommandOutput pseudonymised) => |
| | 4 | 56 | | (long)pseudonymised.EntriesPseudonymised + pseudonymised.TargetsCleared + pseudonymised.TombstonesRemoved; |
| | | 57 | | } |