using MongoDB.Entities; namespace PrivaPub.Infrastructure.Backup { // What a restore did (or why it was abandoned), kept for the administrator's page and for the followers' grace: for // FollowersGrace after a restore the followers' digests (FEP-8fcf) are neither sent nor acted on to undo a follow, since // what the restore lost of either side is not the other's fault. public class RestoreRecord : Entity { public static readonly TimeSpan FollowersGrace = TimeSpan.FromDays(14); public string Backup { get; set; } public DateTime BackupCreatedAt { get; set; } public string PreRestore { get; set; } public string RequestedBy { get; set; } public DateTime RequestedAt { get; set; } public DateTime RestoredAt { get; set; } = DateTime.UtcNow; public int Attempts { get; set; } public bool Abandoned { get; set; }//nothing was changed: the server booted as it was public string Error { get; set; } public RestoreReport Report { get; set; } = new(); static (DateTime Until, DateTime Read) _grace; /// Whether a restore ended less than FollowersGrace ago (read at most once a minute). public static async Task InFollowersGrace(CancellationToken token) { var now = DateTime.UtcNow; var cached = _grace; if (now - cached.Read > TimeSpan.FromMinutes(1)) { var last = await DB.Default.Find().Match(r => !r.Abandoned).Sort(r => r.RestoredAt, Order.Descending).ExecuteFirstAsync(token); cached = (last == default ? DateTime.MinValue : last.RestoredAt + FollowersGrace, now); _grace = cached; } return now < cached.Until; } /// Forgets what was read, so a restore made in this process counts at once. public static void Forget() => _grace = default; } public class RestoreReport { public int Collections { get; set; } public long Documents { get; set; } public int CollectionsDropped { get; set; }//live collections the backup had no documents in public int MediaRestored { get; set; }//files the live directory lacked, brought back public int MediaMissing { get; set; } public int RootsDeleted { get; set; }//deleted since the backup, deleted again public int PersonasDeleted { get; set; } public int GroupsDeleted { get; set; } public int PostsDeleted { get; set; } public List RootsTombstoned { get; set; } = [];//made after the backup: deleted, their names kept public List PersonasTombstoned { get; set; } = []; public List GroupsTombstoned { get; set; } = []; public int PostsGone { get; set; }//local posts made after the backup: 410 from now on public int MediaTrashed { get; set; } public int ProtectiveKept { get; set; }//blocks, mutes, domain blocks, reserved names, reports, filters, deletions kept from since public int SessionsEnded { get; set; } } }