Импорт
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Unit of Work специфичный для модуля Chats.
|
||||
/// </summary>
|
||||
public interface IChatsUnitOfWork : IUnitOfWork
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
public interface IUserDeleterService
|
||||
{
|
||||
Task<Result> DeleteUserAsync(Guid userId, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Abstractions;
|
||||
|
||||
public interface IUserStatusService
|
||||
{
|
||||
bool IsUserOnline(string userId);
|
||||
}
|
||||
@@ -4,8 +4,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
@@ -2,8 +2,8 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Chats.Create;
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
@@ -147,7 +147,9 @@ internal sealed class GetChatsQueryHandler : IQueryHandler<GetChatsQuery, List<C
|
||||
chat.CreatedAt,
|
||||
members,
|
||||
messagesList,
|
||||
unreadCount
|
||||
unreadCount,
|
||||
chat.IsImporting,
|
||||
chat.ImportJobId
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Chats.GetOrCreateFavorites;
|
||||
|
||||
|
||||
+60
-6
@@ -1,12 +1,13 @@
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
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;
|
||||
|
||||
@@ -15,11 +16,19 @@ public record LeaveOrDeleteChatCommand(Guid ChatId, Guid UserId) : ICommand<Succ
|
||||
internal sealed class LeaveOrDeleteChatCommandHandler : ICommandHandler<LeaveOrDeleteChatCommand, SuccessResponse>
|
||||
{
|
||||
private readonly IChatRepository _chatRepository;
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly IChatsUnitOfWork _uow;
|
||||
|
||||
public LeaveOrDeleteChatCommandHandler(IChatRepository chatRepository, IChatsUnitOfWork uow)
|
||||
public LeaveOrDeleteChatCommandHandler(
|
||||
IChatRepository chatRepository,
|
||||
IMessageRepository messageRepository,
|
||||
IFileStorageService fileStorage,
|
||||
IChatsUnitOfWork uow)
|
||||
{
|
||||
_chatRepository = chatRepository;
|
||||
_messageRepository = messageRepository;
|
||||
_fileStorage = fileStorage;
|
||||
_uow = uow;
|
||||
}
|
||||
|
||||
@@ -36,13 +45,18 @@ internal sealed class LeaveOrDeleteChatCommandHandler : ICommandHandler<LeaveOrD
|
||||
return Result.Failure<SuccessResponse>(ChatErrors.Unauthorized);
|
||||
}
|
||||
|
||||
if (chat.Type == ChatType.Group)
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -50,5 +64,45 @@ internal sealed class LeaveOrDeleteChatCommandHandler : ICommandHandler<LeaveOrD
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
|
||||
@@ -2,8 +2,8 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ public record ChatDto(
|
||||
DateTime CreatedAt,
|
||||
List<ChatMemberDto> Members,
|
||||
List<ChatMessageDto> Messages,
|
||||
int UnreadCount
|
||||
int UnreadCount,
|
||||
bool IsImporting = false,
|
||||
Guid? ImportJobId = null
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.DTOs;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.DTOs;
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Settings.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
using global::Knot.Modules.Conversations.Application.Abstractions;
|
||||
using global::Knot.Modules.Conversations.Domain;
|
||||
using global::Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using global::Knot.Contracts.Conversations.Domain;
|
||||
using global::Knot.Modules.Conversations.Infrastructure.SignalR;
|
||||
using global::Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
+19
-7
@@ -5,9 +5,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
@@ -39,12 +39,21 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
}
|
||||
|
||||
DateTime? cursorDate = null;
|
||||
if (!string.IsNullOrEmpty(request.Cursor) && DateTime.TryParse(request.Cursor, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed))
|
||||
long? cursorSequenceId = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(request.Cursor))
|
||||
{
|
||||
cursorDate = parsed.ToUniversalTime();
|
||||
if (long.TryParse(request.Cursor, out var seqId))
|
||||
{
|
||||
cursorSequenceId = seqId;
|
||||
}
|
||||
else if (DateTime.TryParse(request.Cursor, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed))
|
||||
{
|
||||
cursorDate = parsed.ToUniversalTime();
|
||||
}
|
||||
}
|
||||
|
||||
var messages = await _messageRepository.GetChatMessagesCursorAsync(request.ChatId, cursorDate, ChatConstants.DefaultMessageQueryLimit, cancellationToken);
|
||||
var messages = await _messageRepository.GetChatMessagesCursorAsync(request.ChatId, cursorDate, cursorSequenceId, ChatConstants.DefaultMessageQueryLimit, cancellationToken);
|
||||
var result = new List<MessageDetailDto>();
|
||||
|
||||
var userIdsToFetch = new HashSet<Guid>();
|
||||
@@ -88,7 +97,8 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
ReplyToMessageDto? replyToObj = null;
|
||||
if (message.ReplyToId.HasValue && replyMessages.TryGetValue(message.ReplyToId.Value, out var replyMsg))
|
||||
{
|
||||
var senderObj = senders.TryGetValue(replyMsg.SenderId, out var rs)
|
||||
senders.TryGetValue(replyMsg.SenderId, out var rs);
|
||||
var senderObj = rs != null
|
||||
? new MessageSenderDto(rs.Id, rs.Username, rs.DisplayName, rs.Avatar)
|
||||
: null;
|
||||
|
||||
@@ -135,7 +145,9 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
message.CreatedAt,
|
||||
message.SequenceId,
|
||||
message.ForwardedFromId,
|
||||
message.ForwardedFromId.HasValue && senders.TryGetValue(message.ForwardedFromId.Value, out var fwdUser) ? new MessageSenderDto(fwdUser.Id, fwdUser.Username, fwdUser.DisplayName, fwdUser.Avatar) : null,
|
||||
message.ForwardedFromId.HasValue && senders.TryGetValue(message.ForwardedFromId.Value, out var fwdUser)
|
||||
? new MessageSenderDto(fwdUser.Id, fwdUser.Username, fwdUser.DisplayName, fwdUser.Avatar)
|
||||
: null,
|
||||
storyMessage?.StoryId,
|
||||
storyMessage?.StoryMediaUrl,
|
||||
storyMessage?.StoryMediaType,
|
||||
|
||||
+8
-3
@@ -6,9 +6,9 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
@@ -88,7 +88,12 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler<GetSharedMediaQ
|
||||
var filteredMedia = messageMedia.Where(media =>
|
||||
{
|
||||
var mType = media.Type?.ToLower() ?? "file";
|
||||
var isGif = mType == "image" && media.Url != null && (media.Url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || media.Url.EndsWith(".gif", StringComparison.OrdinalIgnoreCase));
|
||||
var filename = media.Filename?.ToLower() ?? "";
|
||||
var url = media.Url?.ToLower() ?? "";
|
||||
|
||||
var isGif = mType == "gif" ||
|
||||
(mType == "image" && (filename.EndsWith(".mp4") || filename.EndsWith(".gif") || url.EndsWith(".gif") || filename.Contains("gif"))) ||
|
||||
(mType == "video" && (filename.Contains("animation") || filename.Contains("gif")));
|
||||
|
||||
if (filterType == "gifs")
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Infrastructure.SignalR;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Modules.Conversations.Infrastructure.SignalR;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using MediatR;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.Read;
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Contracts.Settings.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Conversations.Application.Messages.Send;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
+12
-9
@@ -2,8 +2,8 @@ using System.Text.RegularExpressions;
|
||||
using Knot.Contracts.Auth.Application.Abstractions;
|
||||
using Knot.Contracts.Auth.Domain;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Shared.Kernel.Storage;
|
||||
using MediatR;
|
||||
@@ -55,15 +55,18 @@ internal sealed class DeleteUserCommandHandler : ICommandHandler<DeleteUserComma
|
||||
{
|
||||
foreach (var media in mediaMsg.Media)
|
||||
{
|
||||
bool isUsedElsewhere = allMessages.Any(m => m.Id != msg.Id &&
|
||||
m is MediaMessage mm && mm.Media.Any(ame => ame.Url == media.Url));
|
||||
|
||||
if (!isUsedElsewhere)
|
||||
if (!string.IsNullOrEmpty(media.Url))
|
||||
{
|
||||
var fileId = ExtractFileId(media.Url);
|
||||
if (!string.IsNullOrEmpty(fileId))
|
||||
bool isUsedElsewhere = allMessages.Any(m => m.Id != msg.Id &&
|
||||
m is MediaMessage mm && mm.Media.Any(ame => !string.IsNullOrEmpty(ame.Url) && ame.Url == media.Url));
|
||||
|
||||
if (!isUsedElsewhere)
|
||||
{
|
||||
await _fileStorage.DeleteFileAsync(fileId);
|
||||
var fileId = ExtractFileId(media.Url);
|
||||
if (!string.IsNullOrEmpty(fileId))
|
||||
{
|
||||
await _fileStorage.DeleteFileAsync(fileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user