feat: add file management entities and services for chat message handling

This commit is contained in:
2026-01-05 15:40:06 +03:30
parent 3d2b5ff6bd
commit d2dd67343b
39 changed files with 3548 additions and 41 deletions

View File

@@ -0,0 +1,144 @@
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.Commands.DeleteMessage;
using GozareshgirProgramManager.Application.Modules.TaskChat.Commands.EditMessage;
using GozareshgirProgramManager.Application.Modules.TaskChat.Commands.PinMessage;
using GozareshgirProgramManager.Application.Modules.TaskChat.Commands.SendMessage;
using GozareshgirProgramManager.Application.Modules.TaskChat.Commands.UnpinMessage;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetMessages;
using GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetPinnedMessages;
using GozareshgirProgramManager.Application.Modules.TaskChat.Queries.SearchMessages;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
/// <summary>
/// کنترلر مدیریت چت تسک
/// </summary>
public class TaskChatController : ProgramManagerBaseController
{
private readonly IMediator _mediator;
public TaskChatController(IMediator mediator)
{
_mediator = mediator;
}
/// <summary>
/// دریافت لیست پیام‌های یک تسک
/// </summary>
/// <param name="taskId">شناسه تسک</param>
/// <param name="page">صفحه (پیش‌فرض: 1)</param>
/// <param name="pageSize">تعداد در هر صفحه (پیش‌فرض: 50)</param>
[HttpGet("{taskId:guid}/messages")]
public async Task<ActionResult<OperationResult<PaginationResult<MessageDto>>>> GetMessages(
Guid taskId,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
var query = new GetMessagesQuery(taskId, page, pageSize);
var result = await _mediator.Send(query);
return result;
}
/// <summary>
/// دریافت پیام‌های پین شده یک تسک
/// </summary>
/// <param name="taskId">شناسه تسک</param>
[HttpGet("{taskId:guid}/messages/pinned")]
public async Task<ActionResult<OperationResult<List<MessageDto>>>> GetPinnedMessages(Guid taskId)
{
var query = new GetPinnedMessagesQuery(taskId);
var result = await _mediator.Send(query);
return result;
}
/// <summary>
/// جستجو در پیام‌های یک تسک
/// </summary>
/// <param name="taskId">شناسه تسک</param>
/// <param name="search">متن جستجو</param>
/// <param name="page">صفحه</param>
/// <param name="pageSize">تعداد در هر صفحه</param>
[HttpGet("{taskId:guid}/messages/search")]
public async Task<ActionResult<OperationResult<List<MessageDto>>>> SearchMessages(
Guid taskId,
[FromQuery] string search,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var query = new SearchMessagesQuery(taskId, search, page, pageSize);
var result = await _mediator.Send(query);
return result;
}
/// <summary>
/// ارسال پیام جدید (با یا بدون فایل)
/// </summary>
[HttpPost("messages")]
public async Task<ActionResult<OperationResult<MessageDto>>> SendMessage(
[FromForm] SendMessageCommand command)
{
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// ویرایش پیام (فقط متن)
/// </summary>
/// <param name="messageId">شناسه پیام</param>
/// <param name="request">محتوای جدید</param>
[HttpPut("messages/{messageId:guid}")]
public async Task<ActionResult<OperationResult>> EditMessage(
Guid messageId,
[FromBody] EditMessageRequest request)
{
var command = new EditMessageCommand(messageId, request.NewTextContent);
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// حذف پیام
/// </summary>
/// <param name="messageId">شناسه پیام</param>
[HttpDelete("messages/{messageId:guid}")]
public async Task<ActionResult<OperationResult>> DeleteMessage(Guid messageId)
{
var command = new DeleteMessageCommand(messageId);
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// پین کردن پیام
/// </summary>
/// <param name="messageId">شناسه پیام</param>
[HttpPost("messages/{messageId:guid}/pin")]
public async Task<ActionResult<OperationResult>> PinMessage(Guid messageId)
{
var command = new PinMessageCommand(messageId);
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// برداشتن پین پیام
/// </summary>
/// <param name="messageId">شناسه پیام</param>
[HttpPost("messages/{messageId:guid}/unpin")]
public async Task<ActionResult<OperationResult>> UnpinMessage(Guid messageId)
{
var command = new UnpinMessageCommand(messageId);
var result = await _mediator.Send(command);
return result;
}
}
public class EditMessageRequest
{
public string NewTextContent { get; set; } = string.Empty;
}

View File

@@ -492,6 +492,24 @@ app.UseHttpsRedirection();
app.UseStaticFiles();
// Static files برای فایل‌های آپلود شده
var uploadsPath = builder.Configuration["FileStorage:LocalPath"] ?? Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
if (!Directory.Exists(uploadsPath))
{
Directory.CreateDirectory(uploadsPath);
}
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(uploadsPath),
RequestPath = "/uploads",
OnPrepareResponse = ctx =>
{
// Cache برای فایل‌ها (30 روز)
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=2592000");
}
});
app.UseCookiePolicy();

View File

@@ -64,8 +64,14 @@
"Issuer": "GozareshgirApp",
"Audience": "GozareshgirUsers",
"ExpirationMinutes": 30
},
"FileStorage": {
"BaseUrl": "https://localhost:7032/uploads",
"MaxFileSizeBytes": 104857600,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ],
"AllowedVideoExtensions": [ ".mp4", ".avi", ".mov", ".wmv" ],
"AllowedAudioExtensions": [ ".mp3", ".wav", ".ogg", ".m4a" ],
"AllowedDocumentExtensions": [ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt" ]
}
}

View File

@@ -50,5 +50,13 @@
"Issuer": "GozareshgirApp",
"Audience": "GozareshgirUsers",
"ExpirationMinutes": 30
},
"FileStorage": {
"BaseUrl": "https://api.pm.gozareshgir.ir/uploads",
"MaxFileSizeBytes": 104857600,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ],
"AllowedVideoExtensions": [ ".mp4", ".avi", ".mov", ".wmv" ],
"AllowedAudioExtensions": [ ".mp3", ".wav", ".ogg", ".m4a" ],
"AllowedDocumentExtensions": [ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt" ]
}
}