using GozareshgirProgramManager.Application._Common.Interfaces; using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs; using GozareshgirProgramManager.Application.Services.FileManagement; using GozareshgirProgramManager.Domain._Common.Exceptions; using GozareshgirProgramManager.Domain.TaskChatAgg.Entities; using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories; using GozareshgirProgramManager.Domain.TaskChatAgg.Enums; using GozareshgirProgramManager.Domain.FileManagementAgg.Entities; using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories; using GozareshgirProgramManager.Domain.FileManagementAgg.Enums; using GozareshgirProgramManager.Domain.ProjectAgg.Repositories; using MediatR; using Microsoft.AspNetCore.Http; namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.SendMessage; public record SendMessageCommand( Guid TaskId, MessageType MessageType, string? TextContent, IFormFile? File, Guid? ReplyToMessageId ) : IBaseCommand; public class SendMessageCommandHandler : IBaseCommandHandler { private readonly ITaskChatMessageRepository _messageRepository; private readonly IUploadedFileRepository _fileRepository; private readonly IProjectTaskRepository _taskRepository; private readonly IFileStorageService _fileStorageService; private readonly IThumbnailGeneratorService _thumbnailService; private readonly IAuthHelper _authHelper; public SendMessageCommandHandler( ITaskChatMessageRepository messageRepository, IUploadedFileRepository fileRepository, IProjectTaskRepository taskRepository, IFileStorageService fileStorageService, IThumbnailGeneratorService thumbnailService, IAuthHelper authHelper) { _messageRepository = messageRepository; _fileRepository = fileRepository; _taskRepository = taskRepository; _fileStorageService = fileStorageService; _thumbnailService = thumbnailService; _authHelper = authHelper; } public async Task> Handle(SendMessageCommand request, CancellationToken cancellationToken) { var currentUserId = _authHelper.GetCurrentUserId() ?? throw new UnAuthorizedException("کاربر احراز هویت نشده است"); var task = await _taskRepository.GetByIdAsync(request.TaskId, cancellationToken); if (task == null) { return OperationResult.NotFound("تسک یافت نشد"); } Guid? uploadedFileId = null; if (request.File != null) { if (request.File.Length == 0) { return OperationResult.ValidationError("فایل خالی است"); } const long maxFileSize = 100 * 1024 * 1024; if (request.File.Length > maxFileSize) { return OperationResult.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)"); } var fileType = DetectFileType(request.File.ContentType, Path.GetExtension(request.File.FileName)); var uploadedFile = new UploadedFile( originalFileName: request.File.FileName, fileSizeBytes: request.File.Length, mimeType: request.File.ContentType, fileType: fileType, category: FileCategory.TaskChatMessage, uploadedByUserId: currentUserId, storageProvider: StorageProvider.LocalFileSystem ); await _fileRepository.AddAsync(uploadedFile); await _fileRepository.SaveChangesAsync(); try { using var stream = request.File.OpenReadStream(); var uploadResult = await _fileStorageService.UploadAsync( stream, uploadedFile.UniqueFileName, "TaskChatMessage" ); uploadedFile.CompleteUpload(uploadResult.StoragePath, uploadResult.StorageUrl); if (fileType == FileType.Image) { var dimensions = await _thumbnailService.GetImageDimensionsAsync(uploadResult.StoragePath); if (dimensions.HasValue) { uploadedFile.SetImageDimensions(dimensions.Value.Width, dimensions.Value.Height); } var thumbnail = await _thumbnailService .GenerateImageThumbnailAsync(uploadResult.StoragePath, category: "TaskChatMessage"); if (thumbnail.HasValue) { uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl); } } await _fileRepository.UpdateAsync(uploadedFile); await _fileRepository.SaveChangesAsync(); uploadedFileId = uploadedFile.Id; } catch (Exception ex) { await _fileRepository.DeleteAsync(uploadedFile); await _fileRepository.SaveChangesAsync(); return OperationResult.ValidationError($"خطا در آپلود فایل: {ex.Message}"); } } var message = new TaskChatMessage( taskId: request.TaskId, senderUserId: currentUserId, messageType: request.MessageType, textContent: request.TextContent, uploadedFileId ); if (request.ReplyToMessageId.HasValue) { message.SetReplyTo(request.ReplyToMessageId.Value); } await _messageRepository.AddAsync(message); await _messageRepository.SaveChangesAsync(); if (uploadedFileId.HasValue) { var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value); if (file != null) { file.SetReference("TaskChatMessage", message.Id.ToString()); await _fileRepository.UpdateAsync(file); await _fileRepository.SaveChangesAsync(); } } var dto = new MessageDto { Id = message.Id, TaskId = message.TaskId, SenderUserId = message.SenderUserId, SenderName = "کاربر", MessageType = message.MessageType.ToString(), TextContent = message.TextContent, ReplyToMessageId = message.ReplyToMessageId, IsEdited = message.IsEdited, IsPinned = message.IsPinned, CreationDate = message.CreationDate, IsMine = true }; if (uploadedFileId.HasValue) { var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value); if (file != null) { dto.File = new MessageFileDto { Id = file.Id, FileName = file.OriginalFileName, FileUrl = file.StorageUrl ?? "", FileSizeBytes = file.FileSizeBytes, FileType = file.FileType.ToString(), ThumbnailUrl = file.ThumbnailUrl, ImageWidth = file.ImageWidth, ImageHeight = file.ImageHeight, DurationSeconds = file.DurationSeconds }; } } return OperationResult.Success(dto); } private FileType DetectFileType(string mimeType, string extension) { if (mimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) return FileType.Image; if (mimeType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) return FileType.Video; if (mimeType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase)) return FileType.Audio; if (new[] { ".zip", ".rar", ".7z", ".tar", ".gz" }.Contains(extension.ToLower())) return FileType.Archive; return FileType.Document; } }