Merge branch 'Feature/program-manager/chat'
# Conflicts: # .gitignore # ServiceHost/appsettings.Development.json # ServiceHost/appsettings.json
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Events;
|
||||
using MessageType = GozareshgirProgramManager.Domain.TaskChatAgg.Enums.MessageType;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// پیام چت تسک - Aggregate Root
|
||||
/// هر کسی که به تسک دسترسی داشته باشد میتواند پیامها را ببیند و ارسال کند
|
||||
/// نیازی به مدیریت گروه و ممبر نیست چون دسترسی از طریق خود تسک کنترل میشود
|
||||
/// </summary>
|
||||
public class TaskChatMessage : EntityBase<Guid>
|
||||
{
|
||||
private TaskChatMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public TaskChatMessage(Guid taskId, long senderUserId, MessageType messageType,
|
||||
string? textContent = null,Guid? fileId = null)
|
||||
{
|
||||
TaskId = taskId;
|
||||
SenderUserId = senderUserId;
|
||||
MessageType = messageType;
|
||||
TextContent = textContent;
|
||||
IsEdited = false;
|
||||
IsDeleted = false;
|
||||
IsPinned = false;
|
||||
if (fileId.HasValue)
|
||||
{
|
||||
SetFile(fileId.Value);
|
||||
}
|
||||
ValidateMessage();
|
||||
AddDomainEvent(new TaskChatMessageSentEvent(Id, taskId, senderUserId, messageType));
|
||||
}
|
||||
|
||||
// Reference به Task (Foreign Key فقط - بدون Navigation Property برای جلوگیری از coupling)
|
||||
public Guid TaskId { get; private set; }
|
||||
|
||||
public long SenderUserId { get; private set; }
|
||||
|
||||
public MessageType MessageType { get; private set; }
|
||||
|
||||
// محتوای متنی (برای پیامهای Text و Caption برای فایل/تصویر)
|
||||
public string? TextContent { get; private set; }
|
||||
|
||||
// ارجاع به فایل (برای پیامهای File, Voice, Image, Video)
|
||||
public Guid? FileId { get; private set; }
|
||||
|
||||
// پیام Reply
|
||||
public Guid? ReplyToMessageId { get; private set; }
|
||||
public TaskChatMessage? ReplyToMessage { get; private set; }
|
||||
|
||||
// وضعیت پیام
|
||||
public bool IsEdited { get; private set; }
|
||||
public DateTime? EditedDate { get; private set; }
|
||||
public bool IsDeleted { get; private set; }
|
||||
public DateTime? DeletedDate { get; private set; }
|
||||
public bool IsPinned { get; private set; }
|
||||
public DateTime? PinnedDate { get; private set; }
|
||||
public long? PinnedByUserId { get; private set; }
|
||||
|
||||
private void ValidateMessage()
|
||||
{
|
||||
// ✅ بررسی پیامهای متنی
|
||||
if (MessageType == MessageType.Text && string.IsNullOrWhiteSpace(TextContent))
|
||||
{
|
||||
throw new BadRequestException("پیام متنی نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
// ✅ بررسی پیامهای فایلی - باید FileId داشته باشند
|
||||
if ((MessageType == MessageType.File || MessageType == MessageType.Voice ||
|
||||
MessageType == MessageType.Image || MessageType == MessageType.Video)
|
||||
&& FileId == null)
|
||||
{
|
||||
throw new BadRequestException("پیامهای فایلی باید شناسه فایل داشته باشند");
|
||||
}
|
||||
|
||||
// ✅ بررسی یادداشتهای سیستم - باید محتوای متنی داشته باشند
|
||||
if (MessageType == MessageType.Note && string.IsNullOrWhiteSpace(TextContent))
|
||||
{
|
||||
throw new BadRequestException("یادداشت نمیتواند خالی باشد");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFile(Guid fileId)
|
||||
{
|
||||
if (MessageType != MessageType.File && MessageType != MessageType.Image &&
|
||||
MessageType != MessageType.Video && MessageType != MessageType.Voice)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای پیامهای فایل، تصویر، ویدیو و صدا شناسه فایل تنظیم کرد");
|
||||
}
|
||||
|
||||
FileId = fileId;
|
||||
}
|
||||
|
||||
public void EditMessage(string newTextContent, long editorUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("نمیتوان پیام حذف شده را ویرایش کرد");
|
||||
}
|
||||
|
||||
if (editorUserId != SenderUserId)
|
||||
{
|
||||
throw new BadRequestException("فقط فرستنده میتواند پیام را ویرایش کند");
|
||||
}
|
||||
|
||||
if ((MessageType != MessageType.Text && !string.IsNullOrWhiteSpace(TextContent)))
|
||||
{
|
||||
throw new BadRequestException("فقط پیامهای متنی قابل ویرایش هستند");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newTextContent))
|
||||
{
|
||||
throw new BadRequestException("محتوای پیام نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
TextContent = newTextContent;
|
||||
IsEdited = true;
|
||||
EditedDate = DateTime.Now;
|
||||
AddDomainEvent(new TaskChatMessageEditedEvent(Id, TaskId, editorUserId));
|
||||
}
|
||||
|
||||
public void DeleteMessage(long deleterUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("پیام قبلاً حذف شده است");
|
||||
}
|
||||
|
||||
if (deleterUserId != SenderUserId)
|
||||
{
|
||||
throw new BadRequestException("فقط فرستنده میتواند پیام را حذف کند");
|
||||
}
|
||||
|
||||
IsDeleted = true;
|
||||
DeletedDate = DateTime.Now;
|
||||
AddDomainEvent(new TaskChatMessageDeletedEvent(Id, TaskId, deleterUserId));
|
||||
}
|
||||
|
||||
public void PinMessage(long pinnerUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("نمیتوان پیام حذف شده را پین کرد");
|
||||
}
|
||||
|
||||
if (IsPinned)
|
||||
{
|
||||
throw new BadRequestException("این پیام قبلاً پین شده است");
|
||||
}
|
||||
|
||||
IsPinned = true;
|
||||
PinnedDate = DateTime.Now;
|
||||
PinnedByUserId = pinnerUserId;
|
||||
AddDomainEvent(new TaskChatMessagePinnedEvent(Id, TaskId, pinnerUserId));
|
||||
}
|
||||
|
||||
public void UnpinMessage(long unpinnerUserId)
|
||||
{
|
||||
if (!IsPinned)
|
||||
{
|
||||
throw new BadRequestException("این پیام پین نشده است");
|
||||
}
|
||||
|
||||
IsPinned = false;
|
||||
PinnedDate = null;
|
||||
PinnedByUserId = null;
|
||||
AddDomainEvent(new TaskChatMessageUnpinnedEvent(Id, TaskId, unpinnerUserId));
|
||||
}
|
||||
|
||||
public void SetReplyTo(Guid replyToMessageId)
|
||||
{
|
||||
ReplyToMessageId = replyToMessageId;
|
||||
}
|
||||
|
||||
public bool IsSentBy(long userId)
|
||||
{
|
||||
return SenderUserId == userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع پیام در چت تسک
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
Text = 1, // پیام متنی
|
||||
File = 2, // فایل (اسناد، PDF، و غیره)
|
||||
Image = 3, // تصویر
|
||||
Voice = 4, // پیام صوتی
|
||||
Video = 5, // ویدیو
|
||||
Note = 6, // ✅ یادداشت سیستم (برای زمان اضافی و اطلاعات خودکار)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Events;
|
||||
|
||||
// Message Events
|
||||
public record TaskChatMessageSentEvent(Guid MessageId, Guid TaskId, long SenderUserId, MessageType MessageType) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageEditedEvent(Guid MessageId, Guid TaskId, long EditorUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageDeletedEvent(Guid MessageId, Guid TaskId, long DeleterUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessagePinnedEvent(Guid MessageId, Guid TaskId, long PinnerUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageUnpinnedEvent(Guid MessageId, Guid TaskId, long UnpinnerUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository برای مدیریت پیامهای چت تسک
|
||||
/// </summary>
|
||||
public interface ITaskChatMessageRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// دریافت پیام بر اساس شناسه
|
||||
/// </summary>
|
||||
Task<TaskChatMessage?> GetByIdAsync(Guid messageId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست پیامهای یک تسک (با صفحهبندی)
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetTaskMessagesAsync(Guid taskId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تعداد کل پیامهای یک تسک
|
||||
/// </summary>
|
||||
Task<int> GetTaskMessageCountAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای پین شده یک تسک
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetPinnedMessagesAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت آخرین پیام یک تسک
|
||||
/// </summary>
|
||||
Task<TaskChatMessage?> GetLastMessageAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در پیامهای یک تسک
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> SearchMessagesAsync(Guid taskId, string searchText, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای یک کاربر خاص در یک تسک
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// اضافه کردن پیام جدید
|
||||
/// </summary>
|
||||
Task<TaskChatMessage> AddAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// بهروزرسانی پیام
|
||||
/// </summary>
|
||||
Task UpdateAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// حذف فیزیکی پیام (در صورت نیاز - معمولاً استفاده نمیشود)
|
||||
/// </summary>
|
||||
Task DeleteAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// ذخیره تغییرات
|
||||
/// </summary>
|
||||
Task<int> SaveChangesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود پیام
|
||||
/// </summary>
|
||||
Task<bool> ExistsAsync(Guid messageId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user