using ItWorx.KnowledgeHub.Application.Audit; namespace ItWorx.KnowledgeHub.Application.Operations.UseCases; public sealed record CreateBackupCommand(Guid ActorId, string? CorrelationId); /// Administrator-triggered full backup (docs/18-backup-restore.md). Records /// an audit event on both success and failure — a failed backup is itself an /// operationally significant event (docs/19-observability.md "repeated backup /// failure" alert). public sealed class CreateBackupUseCase(IBackupService backups, IAuditEventRecorder auditEvents, TimeProvider timeProvider, PlatformMetrics? metrics = null) { public async Task ExecuteAsync(CreateBackupCommand command, CancellationToken cancellationToken) { using var measurement = metrics?.Start("backup"); var now = timeProvider.GetUtcNow(); try { var summary = await backups.CreateBackupAsync(cancellationToken); await auditEvents.RecordAsync( new AuditEventRecord( AuditActorKind.User, command.ActorId, Source: "web", Action: "backup.created", TargetType: "backup", TargetId: summary.BackupId, Summary: $"Back-up '{summary.BackupId}' aangemaakt ({summary.TableCount} tabellen, {summary.AttachmentCount} bijlagen).", command.CorrelationId, now), cancellationToken); measurement?.Complete(summary.AttachmentCount); return summary; } catch (Exception exception) when (exception is not OperationCanceledException) { await auditEvents.RecordAsync( new AuditEventRecord( AuditActorKind.User, command.ActorId, Source: "web", Action: "backup.failed", TargetType: null, TargetId: null, // Exception messages from a controlled backup pipeline never carry // secrets/connection strings (docs/19-observability.md "Never log // ... connection strings"); the message itself is safe operator // diagnostic text, not raw exception detail from user input. Summary: $"Back-up mislukt: {exception.GetType().Name}.", command.CorrelationId, now), cancellationToken); throw; } } }