add task section revision command

This commit is contained in:
2026-01-21 18:05:14 +03:30
parent 0e5a0a16ac
commit 4c143d6bbc
11 changed files with 248 additions and 5 deletions

View File

@@ -25,4 +25,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Modules\TaskSectionRevision\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,134 @@
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;
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using Microsoft.AspNetCore.Http;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateTaskSectionRevision;
public record CreateTaskSectionRevisionCommand(string Message, List<IFormFile> Files, Guid SectionId) : IBaseCommand;
public class CreateTaskSectionRevisionCommandHandler : IBaseCommandHandler<CreateTaskSectionRevisionCommand>
{
private readonly ITaskSectionRevisionRepository _revisionRepository;
private readonly IFileStorageService _fileStorageService;
private readonly IAuthHelper _authHelper;
private readonly IUnitOfWork _unitOfWork;
private readonly IUploadedFileRepository _fileRepository;
private readonly IThumbnailGeneratorService _thumbnailService;
public CreateTaskSectionRevisionCommandHandler(ITaskSectionRevisionRepository revisionRepository,
IFileStorageService fileStorageService, IAuthHelper authHelper, IUnitOfWork unitOfWork, IUploadedFileRepository fileRepository, IThumbnailGeneratorService thumbnailService)
{
_revisionRepository = revisionRepository;
_fileStorageService = fileStorageService;
_authHelper = authHelper;
_unitOfWork = unitOfWork;
_fileRepository = fileRepository;
_thumbnailService = thumbnailService;
}
public async Task<OperationResult> Handle(CreateTaskSectionRevisionCommand request,
CancellationToken cancellationToken)
{
var currentId = _authHelper.GetCurrentUserId();
var entity = new TaskSectionRevision(request.SectionId, request.Message, currentId!.Value);
foreach (var file in request.Files)
{
if (file.Length == 0)
{
return OperationResult.ValidationError("فایل خالی است");
}
const long maxFileSize = 100 * 1024 * 1024;
if (file.Length > maxFileSize)
{
return OperationResult.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)");
}
var fileType = DetectFileType(file.ContentType, Path.GetExtension(file.FileName));
var uploadedFile = new UploadedFile(
originalFileName: file.FileName,
fileSizeBytes: file.Length,
mimeType: file.ContentType,
fileType: fileType,
category: FileCategory.TaskSectionRevision,
uploadedByUserId: currentId!.Value,
storageProvider: StorageProvider.LocalFileSystem
);
await _fileRepository.AddAsync(uploadedFile);
await _fileRepository.SaveChangesAsync();
try
{
await using var stream = file.OpenReadStream();
var uploadResult = await _fileStorageService.UploadAsync(
stream,
uploadedFile.UniqueFileName,
"TaskSectionRevision"
);
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: "TaskSectionRevision");
if (thumbnail.HasValue)
{
uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl);
}
}
await _fileRepository.UpdateAsync(uploadedFile);
await _fileRepository.SaveChangesAsync();
var taskRevisionFile = new TaskRevisionFile(uploadedFile.Id);
entity.AddFile(taskRevisionFile);
}
catch (Exception ex)
{
await _fileRepository.DeleteAsync(uploadedFile);
await _fileRepository.SaveChangesAsync();
return OperationResult<MessageDto>.ValidationError($"خطا در آپلود فایل: {ex.Message}");
}
}
await _revisionRepository.CreateAsync(entity);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
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;
}
}

View File

@@ -10,6 +10,7 @@ public enum FileCategory
ProjectDocument = 3, // مستندات پروژه
UserProfilePhoto = 4, // عکس پروفایل کاربر
Report = 5, // گزارش
Other = 6 // سایر
Other = 6, // سایر
TaskSectionRevision
}

View File

@@ -1,14 +1,49 @@
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
namespace GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
public class TaskSectionRevision:EntityBase<Guid>
public class TaskSectionRevision : EntityBase<Guid>
{
public TaskSectionRevision(Guid taskSectionId,
string message, long createdByUserId)
{
TaskSectionId = taskSectionId;
Status = RevisionReviewStatus.Pending;
Message = message;
CreatedByUserId = createdByUserId;
}
public Guid TaskSectionId { get; private set; }
public TaskSectionRevisionStatus Status { get; private set; }
public RevisionReviewStatus Status { get; private set; }
public string Message { get; private set; }
public Guid CreatedByUserId { get; private set; }
public DateTime CreatedAt { get; private set; }
public long CreatedByUserId { get; private set; }
public IReadOnlyCollection<TaskRevisionFile> Files => _files;
private readonly List<TaskRevisionFile> _files = new();
public void AddFile(TaskRevisionFile file)
{
_files.Add(file);
}
}
public class TaskRevisionFile: EntityBase<Guid>
{
public TaskRevisionFile(Guid fileId)
{
FileId = fileId;
}
public Guid FileId { get; private set; }
}
public enum RevisionReviewStatus : short
{
Pending = 1,
Reviewed = 2
}

View File

@@ -0,0 +1,9 @@
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
public interface ITaskSectionRevisionRepository:IRepository<Guid,TaskSectionRevision>
{
}

View File

@@ -100,7 +100,12 @@ public static class DependencyInjection
services.AddScoped<JwtTokenGenerator>();
services.AddScoped<IAuthHelper, AuthHelper>();
//TaskSection Time Request
services.AddScoped<ITaskSectionTimeRequestRepository, TaskSectionTimeRequestRepository>();
//TaskSection Revision
services.AddScoped<ITaskSectionRevisionRepository, TaskSectionRevisionRepository>();
#region ServicesInjection

View File

@@ -0,0 +1,16 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Infrastructure.Persistence._Common;
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories;
public class TaskSectionRevisionRepository:RepositoryBase<Guid,TaskSectionRevision>,ITaskSectionRevisionRepository
{
private readonly ProgramManagerDbContext _programManagerDbContext;
public TaskSectionRevisionRepository(ProgramManagerDbContext programManagerDbContext) : base(programManagerDbContext)
{
_programManagerDbContext = programManagerDbContext;
}
}

View File

@@ -0,0 +1,16 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Infrastructure.Persistence._Common;
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories;
public class TaskSectionTimeRequestRepository:RepositoryBase<Guid,TaskSectionTimeRequest>,ITaskSectionTimeRequestRepository
{
private readonly ProgramManagerDbContext _context;
public TaskSectionTimeRequestRepository(ProgramManagerDbContext context) : base(context)
{
_context = context;
}
}

View File

@@ -0,0 +1,23 @@
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateTaskSectionRevision;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class TaskSectionRevisionController:ProgramManagerBaseController
{
private readonly IMediator _mediator;
public TaskSectionRevisionController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<ActionResult<OperationResult>> CreateTaskRevision([FromForm]CreateTaskSectionRevisionCommand command)
{
var res =await _mediator.Send(command);
return Ok(res);
}
}