using Microsoft.EntityFrameworkCore; using QMax.Api.Data.Entities; namespace QMax.Api.Data; public static class QMaxDatabaseCleanup { public static async Task RemoveLegacyWebFileAttachmentEchoesAsync(QMaxDbContext db) { var connection = db.Database.GetDbConnection(); if (connection.State != System.Data.ConnectionState.Open) { await connection.OpenAsync(); } await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp.LegacyWebFileMessageIds;"); await db.Database.ExecuteSqlRawAsync(""" CREATE TEMP TABLE LegacyWebFileMessageIds ( Id TEXT NOT NULL PRIMARY KEY ); """); await db.Database.ExecuteSqlRawAsync(""" INSERT OR IGNORE INTO LegacyWebFileMessageIds (Id) SELECT DISTINCT legacy.Id FROM Messages AS legacy INNER JOIN MessageAttachments AS legacyAttachment ON legacyAttachment.MessageId = legacy.Id WHERE legacy.Direction = 'Outgoing' AND legacy.ExternalId LIKE 'web-file-%' AND legacyAttachment.Sha256 IS NOT NULL AND legacyAttachment.Sha256 <> '' AND EXISTS ( SELECT 1 FROM Messages AS confirmed INNER JOIN MessageAttachments AS confirmedAttachment ON confirmedAttachment.MessageId = confirmed.Id WHERE confirmed.ChatId = legacy.ChatId AND confirmed.Id <> legacy.Id AND confirmed.Direction = 'Outgoing' AND confirmed.ExternalId IS NOT NULL AND confirmed.ExternalId NOT LIKE 'web-file-%' AND confirmedAttachment.Sha256 = legacyAttachment.Sha256 ); """); await db.Database.ExecuteSqlRawAsync(""" DELETE FROM MessageAttachments WHERE MessageId IN (SELECT Id FROM LegacyWebFileMessageIds); """); await db.Database.ExecuteSqlRawAsync(""" DELETE FROM Messages WHERE Id IN (SELECT Id FROM LegacyWebFileMessageIds); """); await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp.LegacyWebFileMessageIds;"); } public static async Task MergeOutgoingRemoteAttachmentEchoesAsync(QMaxDbContext db) { var connection = db.Database.GetDbConnection(); if (connection.State != System.Data.ConnectionState.Open) { await connection.OpenAsync(); } await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp.OutgoingAttachmentEchoMerges;"); await db.Database.ExecuteSqlRawAsync(""" CREATE TEMP TABLE OutgoingAttachmentEchoMerges ( LocalId TEXT NOT NULL PRIMARY KEY, RemoteId TEXT NOT NULL, ExternalId TEXT NULL, RemoteUrl TEXT NULL ); """); await db.Database.ExecuteSqlRawAsync(""" INSERT OR IGNORE INTO OutgoingAttachmentEchoMerges (LocalId, RemoteId, ExternalId, RemoteUrl) SELECT localAttachment.Id, remoteAttachment.Id, remoteAttachment.ExternalId, remoteAttachment.RemoteUrl FROM Messages AS message INNER JOIN MessageAttachments AS localAttachment ON localAttachment.MessageId = message.Id INNER JOIN MessageAttachments AS remoteAttachment ON remoteAttachment.MessageId = message.Id WHERE message.Direction = 'Outgoing' AND localAttachment.Id <> remoteAttachment.Id AND localAttachment.SortOrder = remoteAttachment.SortOrder AND localAttachment.Kind = remoteAttachment.Kind AND (localAttachment.ExternalId IS NULL OR trim(localAttachment.ExternalId) = '') AND (localAttachment.RemoteUrl IS NULL OR trim(localAttachment.RemoteUrl) = '') AND localAttachment.StorageFileName NOT LIKE 'remote-%' AND ( (remoteAttachment.ExternalId IS NOT NULL AND trim(remoteAttachment.ExternalId) <> '') OR (remoteAttachment.RemoteUrl IS NOT NULL AND trim(remoteAttachment.RemoteUrl) <> '') OR remoteAttachment.StorageFileName LIKE 'remote-%' ); """); await db.Database.ExecuteSqlRawAsync(""" DELETE FROM MessageAttachments WHERE Id IN (SELECT RemoteId FROM OutgoingAttachmentEchoMerges); """); await db.Database.ExecuteSqlRawAsync(""" UPDATE MessageAttachments SET ExternalId = COALESCE(NULLIF(ExternalId, ''), ( SELECT ExternalId FROM OutgoingAttachmentEchoMerges WHERE LocalId = MessageAttachments.Id )), RemoteUrl = COALESCE(NULLIF(RemoteUrl, ''), ( SELECT RemoteUrl FROM OutgoingAttachmentEchoMerges WHERE LocalId = MessageAttachments.Id )) WHERE Id IN (SELECT LocalId FROM OutgoingAttachmentEchoMerges); """); await db.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS temp.OutgoingAttachmentEchoMerges;"); } public static async Task ClearUnreadCountsForLatestOutgoingChatsAsync(QMaxDbContext db) { var unreadChats = await db.Chats .Where(chat => chat.UnreadCount > 0) .ToListAsync(); if (unreadChats.Count == 0) { return; } var changed = false; foreach (var chat in unreadChats) { var chatMessages = await db.Messages .Where(message => message.ChatId == chat.Id && message.DeletedAt == null) .Select(message => new { message.Direction, message.SentAt, message.Id }) .ToListAsync(); var latestMessage = chatMessages .OrderByDescending(message => message.SentAt) .ThenByDescending(message => message.Id) .FirstOrDefault(); if (latestMessage?.Direction != MessageDirection.Outgoing) { continue; } chat.UnreadCount = 0; chat.UpdatedAt = DateTimeOffset.UtcNow; changed = true; } if (changed) { await db.SaveChangesAsync(); } } }