merg from BugSection
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -369,3 +369,6 @@ MigrationBackup/
|
||||
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
|
||||
ServiceHost/Storage
|
||||
|
||||
.env
|
||||
.env.*
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ public static class StaticWorkshopAccounts
|
||||
/// 381 - مهدی قربانی
|
||||
/// 392 - عمار حسن دوست
|
||||
/// 20 - سمیرا الهی نیا
|
||||
/// 322 - ماهان چمنی
|
||||
/// </summary>
|
||||
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476];
|
||||
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476,322];
|
||||
|
||||
/// <summary>
|
||||
/// این تاریخ در جدول اکانت لفت ورک به این معنیست
|
||||
|
||||
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<NuGetAudit>false</NuGetAudit>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,81 @@
|
||||
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._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateBugSection;
|
||||
|
||||
public class CreateBugSectionCommandHandler : IBaseCommandHandler<CreateBugSectionCommand>
|
||||
{
|
||||
readonly IUnitOfWork _unitOfWork;
|
||||
readonly IBugSectionRepository _bugSectionRepository;
|
||||
readonly IFileUploadService _fileUploadService;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public CreateBugSectionCommandHandler(IUnitOfWork unitOfWork, IBugSectionRepository bugSectionRepository,
|
||||
IAuthHelper authHelper, IFileUploadService fileUploadService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_bugSectionRepository = bugSectionRepository;
|
||||
|
||||
_authHelper = authHelper;
|
||||
_fileUploadService = fileUploadService;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(CreateBugSectionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()
|
||||
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
#region Validation
|
||||
if (_bugSectionRepository.Exists(x => x.TaskId == request.TaskId))
|
||||
return OperationResult.Failure("برای این بخش قبلا تسک باگ ایجاد شده است");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.InitialDescription))
|
||||
return OperationResult.Failure("توضیحات باگ خالی است");
|
||||
|
||||
if (request.OriginalAssignedUserId == 0)
|
||||
return OperationResult.Failure("کاربر انتخاب نشده است");
|
||||
#endregion
|
||||
|
||||
var bug = new BugSection(request.TaskId, request.InitialDescription, request.OriginalAssignedUserId,
|
||||
request.Priority);
|
||||
await _bugSectionRepository.CreateAsync(bug);
|
||||
|
||||
if (request.Files.Any())
|
||||
{
|
||||
foreach (var file in request.Files)
|
||||
{
|
||||
var uploadedFile = await _fileUploadService.UploadFileAsync
|
||||
(
|
||||
file,
|
||||
FileCategory.BugSection,
|
||||
currentUserId
|
||||
);
|
||||
if (!uploadedFile.IsSuccess)
|
||||
{
|
||||
return OperationResult.Failure(uploadedFile.ErrorMessage ?? "خطا در آپلود فایل");
|
||||
}
|
||||
bug.AddDocument(new BugDocument(uploadedFile.FileId!.Value));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public record CreateBugSectionCommand(Guid TaskId, string InitialDescription, long OriginalAssignedUserId, ProjectTaskPriority Priority, List<IFormFile> Files) : IBaseCommand;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetBugModalDetails;
|
||||
|
||||
public class GetBugModalDetailsQueryHandler : IBaseQueryHandler<GetBugModalDetailsQuery, GetBugModalDetailsResponse>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
|
||||
public GetBugModalDetailsQueryHandler(IProgramManagerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<GetBugModalDetailsResponse>> Handle(GetBugModalDetailsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var projectTask =await _context.ProjectTasks.Include(ph=>ph.Phase).ThenInclude(p=>p.Project).FirstOrDefaultAsync(x=>x.Id == request.TaskId);
|
||||
if(projectTask == null)
|
||||
return OperationResult<GetBugModalDetailsResponse>.NotFound("بخش یافت نشد");
|
||||
|
||||
var response = new GetBugModalDetailsResponse(projectTask.Name,projectTask.Phase.Name, projectTask.Phase.Project.Name);
|
||||
return OperationResult<GetBugModalDetailsResponse>.Success(response);
|
||||
}
|
||||
}
|
||||
|
||||
public record GetBugModalDetailsQuery(Guid TaskId) : IBaseQuery<GetBugModalDetailsResponse>;
|
||||
|
||||
public record GetBugModalDetailsResponse(string TaskName, string PhaseName, string ProjectName);
|
||||
@@ -28,26 +28,25 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
private readonly ITaskChatMessageRepository _messageRepository;
|
||||
private readonly IUploadedFileRepository _fileRepository;
|
||||
private readonly IProjectTaskRepository _taskRepository;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IThumbnailGeneratorService _thumbnailService;
|
||||
private readonly IFileUploadService _fileUploadService;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public SendMessageCommandHandler(
|
||||
ITaskChatMessageRepository messageRepository,
|
||||
IUploadedFileRepository fileRepository,
|
||||
IProjectTaskRepository taskRepository,
|
||||
IFileStorageService fileStorageService,
|
||||
IThumbnailGeneratorService thumbnailService, IAuthHelper authHelper)
|
||||
IProjectTaskRepository taskRepository,
|
||||
IAuthHelper authHelper,
|
||||
IFileUploadService fileUploadService)
|
||||
{
|
||||
_messageRepository = messageRepository;
|
||||
_fileRepository = fileRepository;
|
||||
_taskRepository = taskRepository;
|
||||
_fileStorageService = fileStorageService;
|
||||
_thumbnailService = thumbnailService;
|
||||
_authHelper = authHelper;
|
||||
_fileUploadService = fileUploadService;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
|
||||
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()
|
||||
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
@@ -57,75 +56,21 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
{
|
||||
return OperationResult<MessageDto>.NotFound("تسک یافت نشد");
|
||||
}
|
||||
|
||||
|
||||
Guid? uploadedFileId = null;
|
||||
if (request.File != null)
|
||||
{
|
||||
if (request.File.Length == 0)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("فایل خالی است");
|
||||
}
|
||||
|
||||
const long maxFileSize = 100 * 1024 * 1024;
|
||||
if (request.File.Length > maxFileSize)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)");
|
||||
}
|
||||
|
||||
var fileType = DetectFileType(request.File.ContentType, Path.GetExtension(request.File.FileName));
|
||||
|
||||
var uploadedFile = new UploadedFile(
|
||||
originalFileName: request.File.FileName,
|
||||
fileSizeBytes: request.File.Length,
|
||||
mimeType: request.File.ContentType,
|
||||
fileType: fileType,
|
||||
category: FileCategory.TaskChatMessage,
|
||||
uploadedByUserId: currentUserId,
|
||||
storageProvider: StorageProvider.LocalFileSystem
|
||||
var uploadedFile = await _fileUploadService.UploadFileAsync
|
||||
(
|
||||
request.File,
|
||||
FileCategory.TaskChatMessage,
|
||||
currentUserId
|
||||
);
|
||||
|
||||
await _fileRepository.AddAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
try
|
||||
if (!uploadedFile.IsSuccess)
|
||||
{
|
||||
using var stream = request.File.OpenReadStream();
|
||||
var uploadResult = await _fileStorageService.UploadAsync(
|
||||
stream,
|
||||
uploadedFile.UniqueFileName,
|
||||
"TaskChatMessage"
|
||||
);
|
||||
|
||||
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: "TaskChatMessage");
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
}
|
||||
}
|
||||
|
||||
await _fileRepository.UpdateAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
uploadedFileId = uploadedFile.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _fileRepository.DeleteAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
return OperationResult<MessageDto>.ValidationError($"خطا در آپلود فایل: {ex.Message}");
|
||||
return OperationResult<MessageDto>.Failure(uploadedFile.ErrorMessage ?? "خطا در آپلود فایل");
|
||||
}
|
||||
uploadedFileId = uploadedFile.FileId!.Value;
|
||||
}
|
||||
|
||||
var message = new TaskChatMessage(
|
||||
@@ -209,4 +154,4 @@ public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand,
|
||||
|
||||
return FileType.Document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس آپلود و مدیریت کامل فایل
|
||||
/// این سرویس تمام مراحل آپلود، ذخیره، تولید thumbnail و... را انجام میدهد
|
||||
/// </summary>
|
||||
public interface IFileUploadService
|
||||
{
|
||||
/// <summary>
|
||||
/// آپلود فایل با تمام مراحل پردازش
|
||||
/// </summary>
|
||||
/// <param name="file">فایل برای آپلود</param>
|
||||
/// <param name="category">دستهبندی فایل</param>
|
||||
/// <param name="uploadedByUserId">شناسه کاربر آپلودکننده</param>
|
||||
/// <param name="maxFileSizeBytes">حداکثر حجم مجاز فایل (پیشفرض: 100MB)</param>
|
||||
/// <returns>شناسه فایل آپلود شده یا null در صورت خطا</returns>
|
||||
Task<FileUploadResult> UploadFileAsync(
|
||||
IFormFile file,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024);
|
||||
|
||||
/// <summary>
|
||||
/// آپلود فایل با Stream
|
||||
/// </summary>
|
||||
Task<FileUploadResult> UploadFileFromStreamAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string contentType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// نتیجه عملیات آپلود فایل
|
||||
/// </summary>
|
||||
public class FileUploadResult
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
public Guid? FileId { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public string? StorageUrl { get; set; }
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
|
||||
public static FileUploadResult Success(Guid fileId, string storageUrl, string? thumbnailUrl = null)
|
||||
{
|
||||
return new FileUploadResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
FileId = fileId,
|
||||
StorageUrl = storageUrl,
|
||||
ThumbnailUrl = thumbnailUrl
|
||||
};
|
||||
}
|
||||
|
||||
public static FileUploadResult Failed(string errorMessage)
|
||||
{
|
||||
return new FileUploadResult
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public interface IProgramManagerDbContext
|
||||
DbSet<TaskSection> TaskSections { get; set; }
|
||||
DbSet<ProjectSection> ProjectSections { get; set; }
|
||||
DbSet<PhaseSection> PhaseSections { get; set; }
|
||||
|
||||
DbSet<BugSection> BugSections { get; set; }
|
||||
DbSet<ProjectTask> ProjectTasks { get; set; }
|
||||
|
||||
DbSet<TaskChatMessage> TaskChatMessages { get; set; }
|
||||
|
||||
@@ -10,7 +10,7 @@ public enum FileCategory
|
||||
ProjectDocument = 3, // مستندات پروژه
|
||||
UserProfilePhoto = 4, // عکس پروفایل کاربر
|
||||
Report = 5, // گزارش
|
||||
Other = 6, // سایر
|
||||
TaskSectionRevision
|
||||
Other = 6, // سایر
|
||||
BugSection = 7, // تسک باگ
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
|
||||
public class BugDocument
|
||||
{
|
||||
|
||||
public BugDocument(Guid fileId)
|
||||
{
|
||||
FileId = fileId;
|
||||
}
|
||||
private BugDocument() { } // EF
|
||||
public Guid Id { get; private set; }
|
||||
public Guid FileId { get; private set; }
|
||||
public BugSection BugSection { get; private set; }
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
|
||||
public class BugSection : EntityBase<Guid>
|
||||
{
|
||||
public BugSection(Guid taskId, string initialDescription, long originalAssignedUserId, ProjectTaskPriority priority)
|
||||
{
|
||||
TaskId = taskId;
|
||||
InitialDescription = initialDescription;
|
||||
Status = TaskSectionStatus.ReadyToStart;
|
||||
OriginalAssignedUserId = originalAssignedUserId;
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// برای EF Core
|
||||
private BugSection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// آی دی تسک - بخش فرعی
|
||||
/// </summary>
|
||||
public Guid TaskId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// توضیحات مدیر
|
||||
/// </summary>
|
||||
public string InitialDescription { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// وضعیت باگ گزارش شده
|
||||
/// </summary>
|
||||
public TaskSectionStatus Status { get; private set; }
|
||||
|
||||
|
||||
// شخصی که برای اولین بار این بخش به او اختصاص داده شده (مالک اصلی)
|
||||
public long OriginalAssignedUserId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// اولویت رسیدگی
|
||||
/// </summary>
|
||||
public ProjectTaskPriority Priority { get; private set; }
|
||||
|
||||
// Navigation to ProjectTask (must be Task level)
|
||||
public ProjectTask ProjectTask { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// لیست مدارک و فایلها
|
||||
/// </summary>
|
||||
public List<BugDocument> BugDocuments { get; private set; } = new();
|
||||
|
||||
public void AddDocument(BugDocument document)
|
||||
{
|
||||
BugDocuments.Add(document);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,8 @@ public class ProjectTask : ProjectHierarchyNode
|
||||
public ProjectTask(string name, Guid phaseId,ProjectTaskPriority priority, string? description = null) : base(name, description)
|
||||
{
|
||||
PhaseId = phaseId;
|
||||
_sections = new List<TaskSection.TaskSection>();
|
||||
_sections = new List<TaskSection>();
|
||||
BugSectionList = new List<BugSection>();
|
||||
Priority = priority;
|
||||
Priority = ProjectTaskPriority.Low;
|
||||
AddDomainEvent(new TaskCreatedEvent(Id, phaseId, name));
|
||||
@@ -27,7 +28,8 @@ public class ProjectTask : ProjectHierarchyNode
|
||||
|
||||
public Guid PhaseId { get; private set; }
|
||||
public ProjectPhase Phase { get; private set; } = null!;
|
||||
public IReadOnlyList<TaskSection.TaskSection> Sections => _sections.AsReadOnly();
|
||||
public IReadOnlyList<TaskSection> Sections => _sections.AsReadOnly();
|
||||
public List<BugSection> BugSectionList { get; set; }
|
||||
|
||||
// Task-specific properties
|
||||
public Enums.TaskStatus Status { get; private set; } = Enums.TaskStatus.NotStarted;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
|
||||
public interface IBugSectionRepository : IRepository<Guid,BugSection>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -76,7 +76,8 @@ public static class DependencyInjection
|
||||
services.AddScoped<ITaskSectionActivityRepository, TaskSectionActivityRepository>();
|
||||
services.AddScoped<IPhaseSectionRepository, PhaseSectionRepository>();
|
||||
services.AddScoped<IProjectSectionRepository, ProjectSectionRepository>();
|
||||
|
||||
services.AddScoped<IBugSectionRepository, BugSectionRepository>();
|
||||
|
||||
services.AddScoped<ISkillRepository, SkillRepository>();
|
||||
|
||||
services.AddScoped<IUserRefreshTokenRepository, UserRefreshTokenRepository>();
|
||||
@@ -88,6 +89,7 @@ public static class DependencyInjection
|
||||
// File Storage Services
|
||||
services.AddScoped<IFileStorageService, Services.FileManagement.LocalFileStorageService>();
|
||||
services.AddScoped<IThumbnailGeneratorService, Services.FileManagement.ThumbnailGeneratorService>();
|
||||
services.AddScoped<IFileUploadService, Services.FileManagement.FileUploadService>();
|
||||
|
||||
// JWT Settings
|
||||
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BugSectioninit : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BugSections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TaskId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
InitialDescription = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
OriginalAssignedUserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Priority = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BugSections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BugSections_ProjectTasks_TaskId",
|
||||
column: x => x.TaskId,
|
||||
principalTable: "ProjectTasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BugDocuments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
FileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BugSectionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BugDocuments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BugDocuments_BugSections_BugSectionId",
|
||||
column: x => x.BugSectionId,
|
||||
principalTable: "BugSections",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BugDocuments_BugSectionId",
|
||||
table: "BugDocuments",
|
||||
column: "BugSectionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BugSections_TaskId",
|
||||
table: "BugSections",
|
||||
column: "TaskId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "BugDocuments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BugSections");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,42 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("UploadedFiles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Phase.PhaseSection", b =>
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.BugSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("InitialDescription")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<long>("OriginalAssignedUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Priority")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("BugSections", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -867,7 +902,44 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("UserRefreshTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Phase.PhaseSection", b =>
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.BugSection", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", "ProjectTask")
|
||||
.WithMany("BugSectionList")
|
||||
.HasForeignKey("TaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.ProjectAgg.Entities.BugDocument", "BugDocuments", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b1.Property<Guid>("BugSectionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b1.Property<Guid>("FileId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("BugSectionId");
|
||||
|
||||
b1.ToTable("BugDocuments", (string)null);
|
||||
|
||||
b1.WithOwner("BugSection")
|
||||
.HasForeignKey("BugSectionId");
|
||||
|
||||
b1.Navigation("BugSection");
|
||||
});
|
||||
|
||||
b.Navigation("BugDocuments");
|
||||
|
||||
b.Navigation("ProjectTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Phase.ProjectPhase", "Phase")
|
||||
.WithMany("PhaseSections")
|
||||
@@ -1170,6 +1242,8 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Task.ProjectTask", b =>
|
||||
{
|
||||
b.Navigation("BugSectionList");
|
||||
|
||||
b.Navigation("Sections");
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class ProgramManagerDbContext : DbContext, IProgramManagerDbContext
|
||||
public DbSet<TaskSection> TaskSections { get; set; } = null!;
|
||||
public DbSet<ProjectSection> ProjectSections { get; set; } = null!;
|
||||
public DbSet<PhaseSection> PhaseSections { get; set; } = null!;
|
||||
public DbSet<BugSection> BugSections { get; set; } = null!;
|
||||
|
||||
// New Hierarchy entities
|
||||
public DbSet<Project> Projects { get; set; } = null!;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Mappings;
|
||||
|
||||
public class BugSectionMapping : IEntityTypeConfiguration<BugSection>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<BugSection> builder)
|
||||
{
|
||||
builder.ToTable("BugSections");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder.Property(x => x.TaskId)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
|
||||
|
||||
builder.Property(x => x.Priority)
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.InitialDescription)
|
||||
.HasMaxLength(500)
|
||||
.IsRequired(false);
|
||||
|
||||
|
||||
builder.OwnsMany(x => x.BugDocuments, navigationBuilder =>
|
||||
{
|
||||
|
||||
navigationBuilder.ToTable("BugDocuments");
|
||||
navigationBuilder.HasKey(x => x.Id);
|
||||
navigationBuilder.WithOwner(x => x.BugSection);
|
||||
});
|
||||
|
||||
// Navigation to ProjectTask (Task level)
|
||||
builder.HasOne(x => x.ProjectTask)
|
||||
.WithMany(t => t.BugSectionList)
|
||||
.HasForeignKey(x => x.TaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,12 @@ public class ProjectTaskMapping : IEntityTypeConfiguration<ProjectTask>
|
||||
.WithOne(s => s.Task)
|
||||
.HasForeignKey(s => s.TaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
|
||||
// One-to-many relationship with BugSections
|
||||
builder.HasMany(t => t.BugSectionList)
|
||||
.WithOne(s => s.ProjectTask)
|
||||
.HasForeignKey(s => s.TaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
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 BugSectionRepository : RepositoryBase<Guid,BugSection>, IBugSectionRepository
|
||||
{
|
||||
private readonly ProgramManagerDbContext _context;
|
||||
public BugSectionRepository(ProgramManagerDbContext context) : base(context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// پیادهسازی سرویس آپلود کامل فایل
|
||||
/// </summary>
|
||||
public class FileUploadService : IFileUploadService
|
||||
{
|
||||
private readonly IUploadedFileRepository _fileRepository;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IThumbnailGeneratorService _thumbnailService;
|
||||
private readonly ILogger<FileUploadService> _logger;
|
||||
|
||||
public FileUploadService(
|
||||
IUploadedFileRepository fileRepository,
|
||||
IFileStorageService fileStorageService,
|
||||
IThumbnailGeneratorService thumbnailService,
|
||||
ILogger<FileUploadService> logger)
|
||||
{
|
||||
_fileRepository = fileRepository;
|
||||
_fileStorageService = fileStorageService;
|
||||
_thumbnailService = thumbnailService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> UploadFileAsync(
|
||||
IFormFile file,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024)
|
||||
{
|
||||
try
|
||||
{
|
||||
// اعتبارسنجی ورودی
|
||||
if (file.Length == 0)
|
||||
{
|
||||
return FileUploadResult.Failed("فایل خالی است");
|
||||
}
|
||||
|
||||
if (file.Length > maxFileSizeBytes)
|
||||
{
|
||||
var maxSizeMb = maxFileSizeBytes / (1024 * 1024);
|
||||
return FileUploadResult.Failed($"حجم فایل بیش از حد مجاز است (حداکثر {maxSizeMb}MB)");
|
||||
}
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
return await UploadFileFromStreamAsync(
|
||||
stream,
|
||||
file.FileName,
|
||||
file.ContentType,
|
||||
category,
|
||||
uploadedByUserId,
|
||||
maxFileSizeBytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "خطا در آپلود فایل {FileName}", file.FileName);
|
||||
return FileUploadResult.Failed($"خطا در آپلود فایل: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> UploadFileFromStreamAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string contentType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
long maxFileSizeBytes = 100 * 1024 * 1024)
|
||||
{
|
||||
UploadedFile? uploadedFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
// تشخیص نوع فایل
|
||||
var fileType = DetectFileType(contentType, Path.GetExtension(fileName));
|
||||
|
||||
// ایجاد رکورد فایل در دیتابیس
|
||||
uploadedFile = new UploadedFile(
|
||||
originalFileName: fileName,
|
||||
fileSizeBytes: fileStream.Length,
|
||||
mimeType: contentType,
|
||||
fileType: fileType,
|
||||
category: category,
|
||||
uploadedByUserId: uploadedByUserId,
|
||||
storageProvider: StorageProvider.LocalFileSystem
|
||||
);
|
||||
|
||||
await _fileRepository.AddAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
// آپلود فایل به استوریج
|
||||
var categoryFolder = category.ToString();
|
||||
var uploadResult = await _fileStorageService.UploadAsync(
|
||||
fileStream,
|
||||
uploadedFile.UniqueFileName,
|
||||
categoryFolder
|
||||
);
|
||||
|
||||
// بهروزرسانی اطلاعات آپلود
|
||||
uploadedFile.CompleteUpload(uploadResult.StoragePath, uploadResult.StorageUrl);
|
||||
|
||||
// پردازشهای خاص بر اساس نوع فایل
|
||||
string? thumbnailUrl = null;
|
||||
if (fileType == FileType.Image)
|
||||
{
|
||||
thumbnailUrl = await ProcessImageAsync(uploadedFile, uploadResult.StoragePath, categoryFolder);
|
||||
}
|
||||
else if (fileType == FileType.Video)
|
||||
{
|
||||
thumbnailUrl = await ProcessVideoAsync(uploadedFile, uploadResult.StoragePath, categoryFolder);
|
||||
}
|
||||
|
||||
// ذخیره تغییرات نهایی
|
||||
await _fileRepository.UpdateAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"فایل {FileName} با شناسه {FileId} با موفقیت آپلود شد",
|
||||
fileName,
|
||||
uploadedFile.Id);
|
||||
|
||||
return FileUploadResult.Success(uploadedFile.Id, uploadResult.StorageUrl, thumbnailUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "خطا در آپلود فایل {FileName}", fileName);
|
||||
|
||||
// در صورت خطا، فایل آپلود شده را حذف کنیم
|
||||
if (uploadedFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _fileRepository.DeleteAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception deleteEx)
|
||||
{
|
||||
_logger.LogError(deleteEx, "خطا در حذف فایل ناموفق {FileId}", uploadedFile.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return FileUploadResult.Failed($"خطا در آپلود فایل: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// پردازش تصویر (ابعاد و thumbnail)
|
||||
/// </summary>
|
||||
private async Task<string?> ProcessImageAsync(UploadedFile file, string storagePath, string categoryFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// دریافت ابعاد تصویر
|
||||
var dimensions = await _thumbnailService.GetImageDimensionsAsync(storagePath);
|
||||
if (dimensions.HasValue)
|
||||
{
|
||||
file.SetImageDimensions(dimensions.Value.Width, dimensions.Value.Height);
|
||||
}
|
||||
|
||||
// تولید thumbnail
|
||||
var thumbnail = await _thumbnailService.GenerateImageThumbnailAsync(
|
||||
storagePath,
|
||||
category: categoryFolder);
|
||||
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
file.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
return thumbnail.Value.ThumbnailUrl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "خطا در پردازش تصویر {FileId}", file.Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// پردازش ویدیو (thumbnail)
|
||||
/// </summary>
|
||||
private async Task<string?> ProcessVideoAsync(UploadedFile file, string storagePath, string categoryFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
// تولید thumbnail از ویدیو
|
||||
var thumbnail = await _thumbnailService.GenerateVideoThumbnailAsync(
|
||||
storagePath,
|
||||
category: categoryFolder);
|
||||
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
file.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
return thumbnail.Value.ThumbnailUrl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "خطا در پردازش ویدیو {FileId}", file.Id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// تشخیص نوع فایل از روی MIME type و extension
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoPendingFullTimeTaskSections;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeDeployStatusProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeTaskPriority;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateBugSection;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.EditProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.TransferSection;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectAssignDetails;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardDetail;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ServiceHost.BaseControllers;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeTaskPriority;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoPendingFullTimeTaskSections;
|
||||
using System.Runtime.InteropServices;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetBugModalDetails;
|
||||
|
||||
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
|
||||
|
||||
@@ -176,5 +178,32 @@ public class ProjectController : ProgramManagerBaseController
|
||||
var res = await _mediator.Send(command);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت اطلاعات مودال ایجاد تسک باگ
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("GetCreateBugModalDetails")]
|
||||
public async Task<ActionResult<OperationResult<GetBugModalDetailsResponse>>> GetCreateBugModalDetails(
|
||||
[FromQuery] GetBugModalDetailsQuery query)
|
||||
{
|
||||
var res = await _mediator.Send(query);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ایجاد تسک باگ
|
||||
/// </summary>
|
||||
/// <param name="commnd"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("CreateBugSection")]
|
||||
public async Task<ActionResult<OperationResult>> CreateBugSection([FromBody] CreateBugSectionCommand commnd)
|
||||
{
|
||||
var res = await _mediator.Send(commnd);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user