diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj b/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj index d28d7b48..cc16cc3a 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/GozareshgirProgramManager.Application.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs new file mode 100644 index 00000000..d524694e --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/CreateTaskSectionRevision/CreateTaskSectionRevisionCommand.cs @@ -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 Files, Guid SectionId) : IBaseCommand; + +public class CreateTaskSectionRevisionCommandHandler : IBaseCommandHandler +{ + 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 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.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; + } +} \ No newline at end of file diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs index 25e7c3f6..e24d441b 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/FileManagementAgg/Enums/FileCategory.cs @@ -10,6 +10,7 @@ public enum FileCategory ProjectDocument = 3, // مستندات پروژه UserProfilePhoto = 4, // عکس پروفایل کاربر Report = 5, // گزارش - Other = 6 // سایر + Other = 6, // سایر + TaskSectionRevision } diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs index 6dfa7c12..07407e12 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Task/TaskSection/TaskSectionRevision.cs @@ -1,14 +1,49 @@ using GozareshgirProgramManager.Domain._Common; +using GozareshgirProgramManager.Domain.FileManagementAgg.Entities; namespace GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection; -public class TaskSectionRevision:EntityBase +public class TaskSectionRevision : EntityBase { + 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 Files => _files; + private readonly List _files = new(); + + public void AddFile(TaskRevisionFile file) + { + _files.Add(file); + } + +} +public class TaskRevisionFile: EntityBase +{ + public TaskRevisionFile(Guid fileId) + { + FileId = fileId; + } + + public Guid FileId { get; private set; } +} + +public enum RevisionReviewStatus : short +{ + Pending = 1, + Reviewed = 2 } \ No newline at end of file diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs new file mode 100644 index 00000000..34397d31 --- /dev/null +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionRevisionRepository.cs @@ -0,0 +1,9 @@ +using GozareshgirProgramManager.Domain._Common; +using GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.TaskSection; + +namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories; + +public interface ITaskSectionRevisionRepository:IRepository +{ + +} \ No newline at end of file diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs index 20618330..7679b07b 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/DependencyInjection.cs @@ -100,7 +100,12 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); + + //TaskSection Time Request + services.AddScoped(); + //TaskSection Revision + services.AddScoped(); #region ServicesInjection diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChat/TaskChatMessageRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChatMessageRepository.cs similarity index 100% rename from ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChat/TaskChatMessageRepository.cs rename to ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskChatMessageRepository.cs diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs new file mode 100644 index 00000000..53290925 --- /dev/null +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRevisionRepository.cs @@ -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,ITaskSectionRevisionRepository +{ + private readonly ProgramManagerDbContext _programManagerDbContext; + public TaskSectionRevisionRepository(ProgramManagerDbContext programManagerDbContext) : base(programManagerDbContext) + { + _programManagerDbContext = programManagerDbContext; + } +} \ No newline at end of file diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs new file mode 100644 index 00000000..48d6b181 --- /dev/null +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionTimeRequestRepository.cs @@ -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,ITaskSectionTimeRequestRepository +{ + private readonly ProgramManagerDbContext _context; + public TaskSectionTimeRequestRepository(ProgramManagerDbContext context) : base(context) + { + _context = context; + } +} \ No newline at end of file diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/FileManagement/UploadedFileRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/UploadedFileRepository.cs similarity index 100% rename from ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/FileManagement/UploadedFileRepository.cs rename to ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/UploadedFileRepository.cs diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs new file mode 100644 index 00000000..04648882 --- /dev/null +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/TaskSectionRevisionController.cs @@ -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> CreateTaskRevision([FromForm]CreateTaskSectionRevisionCommand command) + { + var res =await _mediator.Send(command); + return Ok(res); + } +} \ No newline at end of file