feat: implement TaskChatMessage entity and repository for chat message management
This commit is contained in:
@@ -89,6 +89,9 @@ EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProgramManager", "ProgramManager", "{67AFF7B6-4C4F-464C-A90D-9BDB644D83A9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
ProgramManager\TASKCHAT_ARCHITECTURE.md = ProgramManager\TASKCHAT_ARCHITECTURE.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}"
|
||||
EndProject
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
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)
|
||||
{
|
||||
TaskId = taskId;
|
||||
SenderUserId = senderUserId;
|
||||
MessageType = messageType;
|
||||
TextContent = textContent;
|
||||
IsEdited = false;
|
||||
IsDeleted = false;
|
||||
IsPinned = false;
|
||||
|
||||
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 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; }
|
||||
|
||||
// پیام 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("پیام متنی نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if ((MessageType == MessageType.File || MessageType == MessageType.Voice ||
|
||||
MessageType == MessageType.Image || MessageType == MessageType.Video)
|
||||
&& string.IsNullOrWhiteSpace(FileUrl))
|
||||
{
|
||||
throw new BadRequestException("آدرس فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (MessageType == MessageType.Voice && VoiceDurationSeconds == null)
|
||||
{
|
||||
throw new BadRequestException("مدت زمان صدا باید مشخص شود");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileInfo(string fileUrl, string fileName, long fileSizeBytes, string mimeType)
|
||||
{
|
||||
if (MessageType != MessageType.File && MessageType != MessageType.Image &&
|
||||
MessageType != MessageType.Video && MessageType != MessageType.Voice)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public void EditMessage(string newTextContent, long editorUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("نمیتوان پیام حذف شده را ویرایش کرد");
|
||||
}
|
||||
|
||||
if (editorUserId != SenderUserId)
|
||||
{
|
||||
throw new BadRequestException("فقط فرستنده میتواند پیام را ویرایش کند");
|
||||
}
|
||||
|
||||
if (MessageType != MessageType.Text)
|
||||
{
|
||||
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,14 @@
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع پیام در چت تسک
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
Text = 1, // پیام متنی
|
||||
File = 2, // فایل (اسناد، PDF، و غیره)
|
||||
Image = 3, // تصویر
|
||||
Voice = 4, // پیام صوتی
|
||||
Video = 5, // ویدیو
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// دریافت پیامهای با فایل (تصویر، ویدیو، فایل و...)
|
||||
/// </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