feat: add file management entities and services for chat message handling
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Events;
|
||||
using FileType = GozareshgirProgramManager.Domain.FileManagementAgg.Enums.FileType;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// فایل آپلود شده - Aggregate Root
|
||||
/// مدیریت مرکزی تمام فایلهای سیستم
|
||||
/// </summary>
|
||||
public class UploadedFile : EntityBase<Guid>
|
||||
{
|
||||
private UploadedFile()
|
||||
{
|
||||
}
|
||||
|
||||
public UploadedFile(
|
||||
string originalFileName,
|
||||
long fileSizeBytes,
|
||||
string mimeType,
|
||||
FileType fileType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
StorageProvider storageProvider = StorageProvider.LocalFileSystem)
|
||||
{
|
||||
OriginalFileName = originalFileName;
|
||||
FileSizeBytes = fileSizeBytes;
|
||||
MimeType = mimeType;
|
||||
FileType = fileType;
|
||||
Category = category;
|
||||
UploadedByUserId = uploadedByUserId;
|
||||
UploadDate = DateTime.Now;
|
||||
StorageProvider = storageProvider;
|
||||
Status = FileStatus.Uploading;
|
||||
|
||||
// Generate unique file name
|
||||
FileExtension = Path.GetExtension(originalFileName);
|
||||
UniqueFileName = $"{Guid.NewGuid()}{FileExtension}";
|
||||
|
||||
ValidateFile();
|
||||
AddDomainEvent(new FileUploadStartedEvent(Id, originalFileName, uploadedByUserId));
|
||||
}
|
||||
|
||||
// اطلاعات فایل
|
||||
public string OriginalFileName { get; private set; } = string.Empty;
|
||||
public string UniqueFileName { get; private set; } = string.Empty;
|
||||
public string FileExtension { get; private set; } = string.Empty;
|
||||
public long FileSizeBytes { get; private set; }
|
||||
public string MimeType { get; private set; } = string.Empty;
|
||||
public FileType FileType { get; private set; }
|
||||
public FileCategory Category { get; private set; }
|
||||
|
||||
// ذخیرهسازی
|
||||
public StorageProvider StorageProvider { get; private set; }
|
||||
public string? StoragePath { get; private set; }
|
||||
public string? StorageUrl { get; private set; }
|
||||
public string? ThumbnailUrl { get; private set; }
|
||||
|
||||
// متادیتا
|
||||
public long UploadedByUserId { get; private set; }
|
||||
public DateTime UploadDate { get; private set; }
|
||||
public FileStatus Status { get; private set; }
|
||||
|
||||
// اطلاعات تصویر (اختیاری - برای Image)
|
||||
public int? ImageWidth { get; private set; }
|
||||
public int? ImageHeight { get; private set; }
|
||||
|
||||
// اطلاعات صوت/ویدیو (اختیاری)
|
||||
public int? DurationSeconds { get; private set; }
|
||||
|
||||
// امنیت
|
||||
public DateTime? VirusScanDate { get; private set; }
|
||||
public bool? IsVirusScanPassed { get; private set; }
|
||||
public string? VirusScanResult { get; private set; }
|
||||
|
||||
// Soft Delete
|
||||
public bool IsDeleted { get; private set; }
|
||||
public DateTime? DeletedDate { get; private set; }
|
||||
public long? DeletedByUserId { get; private set; }
|
||||
|
||||
// Reference tracking (چه entityهایی از این فایل استفاده میکنند)
|
||||
public string? ReferenceEntityType { get; private set; }
|
||||
public string? ReferenceEntityId { get; private set; }
|
||||
|
||||
private void ValidateFile()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(OriginalFileName))
|
||||
{
|
||||
throw new BadRequestException("نام فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (FileSizeBytes <= 0)
|
||||
{
|
||||
throw new BadRequestException("حجم فایل باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(MimeType))
|
||||
{
|
||||
throw new BadRequestException("نوع MIME فایل باید مشخص شود");
|
||||
}
|
||||
|
||||
// محدودیت حجم (مثلاً 100MB)
|
||||
const long maxSizeBytes = 100 * 1024 * 1024; // 100MB
|
||||
if (FileSizeBytes > maxSizeBytes)
|
||||
{
|
||||
throw new BadRequestException($"حجم فایل نباید بیشتر از {maxSizeBytes / (1024 * 1024)} مگابایت باشد");
|
||||
}
|
||||
}
|
||||
|
||||
public void CompleteUpload(string storagePath, string storageUrl)
|
||||
{
|
||||
if (Status != FileStatus.Uploading)
|
||||
{
|
||||
throw new BadRequestException("فایل قبلاً آپلود شده است");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(storagePath))
|
||||
{
|
||||
throw new BadRequestException("مسیر ذخیرهسازی نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(storageUrl))
|
||||
{
|
||||
throw new BadRequestException("URL فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
StoragePath = storagePath;
|
||||
StorageUrl = storageUrl;
|
||||
Status = FileStatus.Active;
|
||||
|
||||
AddDomainEvent(new FileUploadCompletedEvent(Id, OriginalFileName, StorageUrl, UploadedByUserId));
|
||||
}
|
||||
|
||||
public void SetThumbnail(string thumbnailUrl)
|
||||
{
|
||||
if (FileType != FileType.Image && FileType != FileType.Video)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای تصاویر و ویدیوها thumbnail تنظیم کرد");
|
||||
}
|
||||
|
||||
ThumbnailUrl = thumbnailUrl;
|
||||
}
|
||||
|
||||
public void SetImageDimensions(int width, int height)
|
||||
{
|
||||
if (FileType != FileType.Image)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای تصاویر ابعاد تنظیم کرد");
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
throw new BadRequestException("ابعاد تصویر باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
ImageWidth = width;
|
||||
ImageHeight = height;
|
||||
}
|
||||
|
||||
public void SetDuration(int durationSeconds)
|
||||
{
|
||||
if (FileType != FileType.Audio && FileType != FileType.Video)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای فایلهای صوتی و تصویری مدت زمان تنظیم کرد");
|
||||
}
|
||||
|
||||
if (durationSeconds <= 0)
|
||||
{
|
||||
throw new BadRequestException("مدت زمان باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
DurationSeconds = durationSeconds;
|
||||
}
|
||||
|
||||
public void MarkAsDeleted(long deletedByUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("فایل قبلاً حذف شده است");
|
||||
}
|
||||
|
||||
IsDeleted = true;
|
||||
DeletedDate = DateTime.Now;
|
||||
DeletedByUserId = deletedByUserId;
|
||||
Status = FileStatus.Deleted;
|
||||
|
||||
AddDomainEvent(new FileDeletedEvent(Id, OriginalFileName, deletedByUserId));
|
||||
}
|
||||
|
||||
public void SetReference(string entityType, string entityId)
|
||||
{
|
||||
ReferenceEntityType = entityType;
|
||||
ReferenceEntityId = entityId;
|
||||
}
|
||||
|
||||
public bool IsImage()
|
||||
{
|
||||
return FileType == FileType.Image;
|
||||
}
|
||||
|
||||
public bool IsVideo()
|
||||
{
|
||||
return FileType == FileType.Video;
|
||||
}
|
||||
|
||||
public bool IsAudio()
|
||||
{
|
||||
return FileType == FileType.Audio;
|
||||
}
|
||||
|
||||
public bool IsDocument()
|
||||
{
|
||||
return FileType == FileType.Document;
|
||||
}
|
||||
|
||||
public bool IsUploadedBy(long userId)
|
||||
{
|
||||
return UploadedByUserId == userId;
|
||||
}
|
||||
|
||||
public bool IsActive()
|
||||
{
|
||||
return Status == FileStatus.Active && !IsDeleted;
|
||||
}
|
||||
|
||||
public string GetFileSizeFormatted()
|
||||
{
|
||||
const long kb = 1024;
|
||||
const long mb = kb * 1024;
|
||||
const long gb = mb * 1024;
|
||||
|
||||
if (FileSizeBytes >= gb)
|
||||
return $"{FileSizeBytes / (double)gb:F2} GB";
|
||||
if (FileSizeBytes >= mb)
|
||||
return $"{FileSizeBytes / (double)mb:F2} MB";
|
||||
if (FileSizeBytes >= kb)
|
||||
return $"{FileSizeBytes / (double)kb:F2} KB";
|
||||
|
||||
return $"{FileSizeBytes} Bytes";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// دستهبندی فایل - مشخص میکند فایل در کجا استفاده شده
|
||||
/// </summary>
|
||||
public enum FileCategory
|
||||
{
|
||||
TaskChatMessage = 1, // پیام چت تسک
|
||||
TaskAttachment = 2, // ضمیمه تسک
|
||||
ProjectDocument = 3, // مستندات پروژه
|
||||
UserProfilePhoto = 4, // عکس پروفایل کاربر
|
||||
Report = 5, // گزارش
|
||||
Other = 6 // سایر
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// وضعیت فایل
|
||||
/// </summary>
|
||||
public enum FileStatus
|
||||
{
|
||||
Uploading = 1, // در حال آپلود
|
||||
Active = 2, // فعال و قابل استفاده
|
||||
Deleted = 5, // حذف شده (Soft Delete)
|
||||
Archived = 6 // آرشیو شده
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع فایل
|
||||
/// </summary>
|
||||
public enum FileType
|
||||
{
|
||||
Document = 1, // اسناد (PDF, Word, Excel, etc.)
|
||||
Image = 2, // تصویر
|
||||
Video = 3, // ویدیو
|
||||
Audio = 4, // صوت
|
||||
Archive = 5, // فایل فشرده (ZIP, RAR)
|
||||
Other = 6 // سایر
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع ذخیرهساز فایل
|
||||
/// </summary>
|
||||
public enum StorageProvider
|
||||
{
|
||||
LocalFileSystem = 1, // دیسک محلی سرور
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Events;
|
||||
|
||||
// File Upload Events
|
||||
public record FileUploadStartedEvent(Guid FileId, string FileName, long UploadedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileUploadCompletedEvent(Guid FileId, string FileName, string StorageUrl, long UploadedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileDeletedEvent(Guid FileId, string FileName, long DeletedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// Virus Scan Events
|
||||
public record FileQuarantinedEvent(Guid FileId, string FileName) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileVirusScanPassedEvent(Guid FileId, string FileName) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileInfectedEvent(Guid FileId, string FileName, string ScanResult) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository برای مدیریت فایلهای آپلود شده
|
||||
/// </summary>
|
||||
public interface IUploadedFileRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// دریافت فایل بر اساس شناسه
|
||||
/// </summary>
|
||||
Task<UploadedFile?> GetByIdAsync(Guid fileId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایل بر اساس نام یکتا
|
||||
/// </summary>
|
||||
Task<UploadedFile?> GetByUniqueFileNameAsync(string uniqueFileName);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست فایلهای یک کاربر
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetUserFilesAsync(long userId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای یک دسته خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByCategoryAsync(FileCategory category, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای با وضعیت خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByStatusAsync(FileStatus status, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای یک Reference خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByReferenceAsync(string entityType, string entityId);
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در فایلها بر اساس نام
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> SearchByNameAsync(string searchTerm, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تعداد کل فایلهای یک کاربر
|
||||
/// </summary>
|
||||
Task<int> GetUserFilesCountAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت مجموع حجم فایلهای یک کاربر (به بایت)
|
||||
/// </summary>
|
||||
Task<long> GetUserTotalFileSizeAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای منقضی شده برای پاکسازی
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetExpiredFilesAsync(DateTime olderThan);
|
||||
|
||||
/// <summary>
|
||||
/// اضافه کردن فایل جدید
|
||||
/// </summary>
|
||||
Task<UploadedFile> AddAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// بهروزرسانی فایل
|
||||
/// </summary>
|
||||
Task UpdateAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// حذف فیزیکی فایل (فقط برای cleanup)
|
||||
/// </summary>
|
||||
Task DeleteAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// ذخیره تغییرات
|
||||
/// </summary>
|
||||
Task<int> SaveChangesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود فایل
|
||||
/// </summary>
|
||||
Task<bool> ExistsAsync(Guid fileId);
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود فایل با نام یکتا
|
||||
/// </summary>
|
||||
Task<bool> ExistsByUniqueFileNameAsync(string uniqueFileName);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,8 @@ public class TaskChatMessage : EntityBase<Guid>
|
||||
// محتوای متنی (برای پیامهای Text و Caption برای فایل/تصویر)
|
||||
public string? TextContent { get; private set; }
|
||||
|
||||
// اطلاعات فایل (برای پیامهای File, Voice, Image, Video)
|
||||
public string? FileUrl { get; private set; }
|
||||
public string? FileName { get; private set; }
|
||||
public long? FileSizeBytes { get; private set; }
|
||||
public string? FileMimeType { get; private set; }
|
||||
|
||||
// اطلاعات صوت (برای Voice)
|
||||
public int? VoiceDurationSeconds { get; private set; }
|
||||
// ارجاع به فایل (برای پیامهای File, Voice, Image, Video)
|
||||
public Guid? FileId { get; private set; }
|
||||
|
||||
// پیام Reply
|
||||
public Guid? ReplyToMessageId { get; private set; }
|
||||
@@ -71,44 +65,21 @@ public class TaskChatMessage : EntityBase<Guid>
|
||||
|
||||
if ((MessageType == MessageType.File || MessageType == MessageType.Voice ||
|
||||
MessageType == MessageType.Image || MessageType == MessageType.Video)
|
||||
&& string.IsNullOrWhiteSpace(FileUrl))
|
||||
&& FileId == null)
|
||||
{
|
||||
throw new BadRequestException("آدرس فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (MessageType == MessageType.Voice && VoiceDurationSeconds == null)
|
||||
{
|
||||
throw new BadRequestException("مدت زمان صدا باید مشخص شود");
|
||||
throw new BadRequestException("پیامهای فایلی باید شناسه فایل داشته باشند");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileInfo(string fileUrl, string fileName, long fileSizeBytes, string mimeType)
|
||||
public void SetFile(Guid fileId)
|
||||
{
|
||||
if (MessageType != MessageType.File && MessageType != MessageType.Image &&
|
||||
MessageType != MessageType.Video && MessageType != MessageType.Voice)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای پیامهای فایل، تصویر، ویدیو و صدا اطلاعات فایل تنظیم کرد");
|
||||
throw new BadRequestException("فقط میتوان برای پیامهای فایل، تصویر، ویدیو و صدا شناسه فایل تنظیم کرد");
|
||||
}
|
||||
|
||||
FileUrl = fileUrl;
|
||||
FileName = fileName;
|
||||
FileSizeBytes = fileSizeBytes;
|
||||
FileMimeType = mimeType;
|
||||
}
|
||||
|
||||
public void SetVoiceDuration(int durationSeconds)
|
||||
{
|
||||
if (MessageType != MessageType.Voice)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای پیامهای صوتی مدت زمان تنظیم کرد");
|
||||
}
|
||||
|
||||
if (durationSeconds <= 0)
|
||||
{
|
||||
throw new BadRequestException("مدت زمان صدا باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
VoiceDurationSeconds = durationSeconds;
|
||||
FileId = fileId;
|
||||
}
|
||||
|
||||
public void EditMessage(string newTextContent, long editorUserId)
|
||||
|
||||
@@ -43,7 +43,7 @@ public interface ITaskChatMessageRepository
|
||||
Task<List<TaskChatMessage>> GetUserMessagesAsync(Guid taskId, long userId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای با فایل (تصویر، ویدیو، فایل و...)
|
||||
/// دریافت پیامهای با فایل (تصویر، ویدیو، فایل و...) - پیامهایی که FileId دارند
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetMediaMessagesAsync(Guid taskId, int pageNumber, int pageSize);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user