using System; using System.Threading; using System.Threading.Tasks; using Knot.Shared.Kernel; using Knot.Contracts.Conversations.Domain; using Knot.Contracts.Conversations.Application.Abstractions; using MediatR; using System.Linq; using Knot.Contracts.Messaging.Application.Abstractions; using Knot.Shared.Kernel.Storage; namespace Knot.Modules.Conversations.Application.Chats.LeaveOrDelete; public record LeaveOrDeleteChatCommand(Guid ChatId, Guid UserId) : ICommand; internal sealed class LeaveOrDeleteChatCommandHandler : ICommandHandler { private readonly IChatRepository _chatRepository; private readonly IMessageRepository _messageRepository; private readonly IFileStorageService _fileStorage; private readonly IChatsUnitOfWork _uow; public LeaveOrDeleteChatCommandHandler( IChatRepository chatRepository, IMessageRepository messageRepository, IFileStorageService fileStorage, IChatsUnitOfWork uow) { _chatRepository = chatRepository; _messageRepository = messageRepository; _fileStorage = fileStorage; _uow = uow; } public async Task> Handle(LeaveOrDeleteChatCommand request, CancellationToken cancellationToken) { var chat = await _chatRepository.GetByIdAsync(request.ChatId, cancellationToken); if (chat == null) { return Result.Success(new SuccessResponse(true)); } if (!chat.Members.Any(m => m.UserId == request.UserId)) { return Result.Failure(ChatErrors.Unauthorized); } // If it's a private chat or the last member leaving a group, delete everything bool shouldDeleteEverything = chat.Type != ChatType.Group || chat.Members.Count <= 1; if (chat.Type == ChatType.Group && !shouldDeleteEverything) { chat.RemoveMember(request.UserId); _chatRepository.Update(chat); } else { // DELETE ALL MESSAGES AND FILES FIRST await DeleteChatMediaAndMessagesAsync(chat.Id, cancellationToken); _chatRepository.Remove(chat); } await _uow.SaveChangesAsync(cancellationToken); return Result.Success(new SuccessResponse(true)); } private async Task DeleteChatMediaAndMessagesAsync(Guid chatId, CancellationToken ct) { try { // Get all messages directly from Mongo (not paged) var messages = await _messageRepository.GetChatMessagesAsync(chatId, int.MaxValue, 0, ct); foreach (var msg in messages) { if (msg is Knot.Contracts.Messaging.Domain.MediaMessage mediaMsg) { foreach (var media in mediaMsg.Media) { if (!string.IsNullOrEmpty(media.Url)) { var fileId = ExtractFileId(media.Url); if (!string.IsNullOrEmpty(fileId)) { await _fileStorage.DeleteFileAsync(fileId); } } } } } await _messageRepository.DeleteChatMessagesAsync(chatId, ct); } catch (Exception ex) { // Log if possible, but don't fail chat deletion Console.WriteLine($"[Cleanup] Error deleting chat media: {ex.Message}"); } } private string? ExtractFileId(string url) { var lastSlash = url.LastIndexOf('/'); if (lastSlash == -1) return null; var id = url[(lastSlash + 1)..]; if (id.Contains('?')) id = id[..id.IndexOf('?')]; return id; } }