feat: add file management entities and services for chat message handling
This commit is contained in:
@@ -4,9 +4,11 @@
|
||||
using FluentValidation;
|
||||
using GozareshgirProgramManager.Application._Common.Behaviors;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.CheckoutAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
|
||||
@@ -14,6 +16,7 @@ using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
@@ -82,6 +85,14 @@ public static class DependencyInjection
|
||||
|
||||
services.AddScoped<IUserRefreshTokenRepository, UserRefreshTokenRepository>();
|
||||
|
||||
// File Management & Task Chat
|
||||
services.AddScoped<IUploadedFileRepository, Persistence.Repositories.FileManagement.UploadedFileRepository>();
|
||||
services.AddScoped<ITaskChatMessageRepository, Persistence.Repositories.TaskChat.TaskChatMessageRepository>();
|
||||
|
||||
// File Storage Services
|
||||
services.AddScoped<IFileStorageService, Services.FileManagement.LocalFileStorageService>();
|
||||
services.AddScoped<IThumbnailGeneratorService, Services.FileManagement.ThumbnailGeneratorService>();
|
||||
|
||||
// JWT Settings
|
||||
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
|
||||
|
||||
|
||||
@@ -16,10 +16,19 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<!--<PackageReference Include="System.Text.Encodings.Web" Version="10.0.0" />-->
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Application\GozareshgirProgramManager.Application\GozareshgirProgramManager.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.AspNetCore.Hosting.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Hosting.Abstractions">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.Extensions.Hosting.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class addtaskchatuploadedfile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TaskChatMessages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TaskId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SenderUserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
MessageType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
TextContent = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
|
||||
FileId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
ReplyToMessageId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsEdited = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
EditedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeletedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsPinned = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
PinnedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
PinnedByUserId = table.Column<long>(type: "bigint", nullable: true),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TaskChatMessages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TaskChatMessages_TaskChatMessages_ReplyToMessageId",
|
||||
column: x => x.ReplyToMessageId,
|
||||
principalTable: "TaskChatMessages",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UploadedFiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
OriginalFileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
UniqueFileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
FileExtension = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
FileSizeBytes = table.Column<long>(type: "bigint", nullable: false),
|
||||
MimeType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
FileType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
StorageProvider = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
StoragePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
StorageUrl = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
ThumbnailUrl = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
UploadedByUserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
UploadDate = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
ImageWidth = table.Column<int>(type: "int", nullable: true),
|
||||
ImageHeight = table.Column<int>(type: "int", nullable: true),
|
||||
DurationSeconds = table.Column<int>(type: "int", nullable: true),
|
||||
VirusScanDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsVirusScanPassed = table.Column<bool>(type: "bit", nullable: true),
|
||||
VirusScanResult = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeletedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedByUserId = table.Column<long>(type: "bigint", nullable: true),
|
||||
ReferenceEntityType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
ReferenceEntityId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UploadedFiles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_CreationDate",
|
||||
table: "TaskChatMessages",
|
||||
column: "CreationDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_FileId",
|
||||
table: "TaskChatMessages",
|
||||
column: "FileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_IsDeleted",
|
||||
table: "TaskChatMessages",
|
||||
column: "IsDeleted");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_ReplyToMessageId",
|
||||
table: "TaskChatMessages",
|
||||
column: "ReplyToMessageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_SenderUserId",
|
||||
table: "TaskChatMessages",
|
||||
column: "SenderUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_TaskId",
|
||||
table: "TaskChatMessages",
|
||||
column: "TaskId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_TaskId_IsPinned",
|
||||
table: "TaskChatMessages",
|
||||
columns: new[] { "TaskId", "IsPinned" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_Category",
|
||||
table: "UploadedFiles",
|
||||
column: "Category");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_IsDeleted",
|
||||
table: "UploadedFiles",
|
||||
column: "IsDeleted");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_ReferenceEntityType_ReferenceEntityId",
|
||||
table: "UploadedFiles",
|
||||
columns: new[] { "ReferenceEntityType", "ReferenceEntityId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_Status",
|
||||
table: "UploadedFiles",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_UniqueFileName",
|
||||
table: "UploadedFiles",
|
||||
column: "UniqueFileName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_UploadedByUserId",
|
||||
table: "UploadedFiles",
|
||||
column: "UploadedByUserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TaskChatMessages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UploadedFiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,131 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("Customers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.FileManagementAgg.Entities.UploadedFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long?>("DeletedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("DeletedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DurationSeconds")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("FileExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<long>("FileSizeBytes")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("FileType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int?>("ImageHeight")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ImageWidth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool?>("IsVirusScanPassed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ReferenceEntityId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("ReferenceEntityType")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("StoragePath")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("StorageProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("UniqueFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("UploadDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long>("UploadedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("VirusScanDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("VirusScanResult")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category");
|
||||
|
||||
b.HasIndex("IsDeleted");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("UniqueFileName")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UploadedByUserId");
|
||||
|
||||
b.HasIndex("ReferenceEntityType", "ReferenceEntityId");
|
||||
|
||||
b.ToTable("UploadedFiles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -495,6 +620,81 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("Skills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("DeletedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("EditedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("FileId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsEdited")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsPinned")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<string>("MessageType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<long?>("PinnedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("PinnedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("ReplyToMessageId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<long>("SenderUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("TextContent")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("nvarchar(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreationDate");
|
||||
|
||||
b.HasIndex("FileId");
|
||||
|
||||
b.HasIndex("IsDeleted");
|
||||
|
||||
b.HasIndex("ReplyToMessageId");
|
||||
|
||||
b.HasIndex("SenderUserId");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.HasIndex("TaskId", "IsPinned");
|
||||
|
||||
b.ToTable("TaskChatMessages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -779,6 +979,16 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.Navigation("WorkingHoursList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", "ReplyToMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ReplyToMessageId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.Navigation("ReplyToMessage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.RoleUserAgg.RoleUser", "RoleUser", b1 =>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Domain.CheckoutAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.RoleUserAgg;
|
||||
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
@@ -40,6 +42,13 @@ public class ProgramManagerDbContext : DbContext, IProgramManagerDbContext
|
||||
public DbSet<Role> Roles { get; set; } = null!;
|
||||
|
||||
public DbSet<Skill> Skills { get; set; } = null!;
|
||||
|
||||
// File Management
|
||||
public DbSet<UploadedFile> UploadedFiles { get; set; } = null!;
|
||||
|
||||
// Task Chat
|
||||
public DbSet<TaskChatMessage> TaskChatMessages { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProgramManagerDbContext).Assembly);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Mappings;
|
||||
|
||||
public class TaskChatMessageMapping : IEntityTypeConfiguration<TaskChatMessage>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TaskChatMessage> builder)
|
||||
{
|
||||
builder.ToTable("TaskChatMessages");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
// Task Reference
|
||||
builder.Property(x => x.TaskId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.TaskId);
|
||||
|
||||
// Sender
|
||||
builder.Property(x => x.SenderUserId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.SenderUserId);
|
||||
|
||||
// Message Type
|
||||
builder.Property(x => x.MessageType)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
// Content
|
||||
builder.Property(x => x.TextContent)
|
||||
.HasMaxLength(4000);
|
||||
|
||||
// File Reference
|
||||
builder.Property(x => x.FileId);
|
||||
|
||||
builder.HasIndex(x => x.FileId);
|
||||
|
||||
// Reply
|
||||
builder.Property(x => x.ReplyToMessageId);
|
||||
|
||||
builder.HasOne(x => x.ReplyToMessage)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.ReplyToMessageId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
// Status
|
||||
builder.Property(x => x.IsEdited)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.EditedDate);
|
||||
|
||||
builder.Property(x => x.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.DeletedDate);
|
||||
|
||||
builder.HasIndex(x => x.IsDeleted);
|
||||
|
||||
// Pin
|
||||
builder.Property(x => x.IsPinned)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.PinnedDate);
|
||||
builder.Property(x => x.PinnedByUserId);
|
||||
|
||||
builder.HasIndex(x => new { x.TaskId, x.IsPinned });
|
||||
|
||||
// Audit
|
||||
builder.Property(x => x.CreationDate)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.CreationDate);
|
||||
|
||||
// Query Filter - پیامهای حذف نشده
|
||||
builder.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Mappings;
|
||||
|
||||
public class UploadedFileMapping : IEntityTypeConfiguration<UploadedFile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<UploadedFile> builder)
|
||||
{
|
||||
builder.ToTable("UploadedFiles");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
// اطلاعات فایل
|
||||
builder.Property(x => x.OriginalFileName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.Property(x => x.UniqueFileName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.HasIndex(x => x.UniqueFileName)
|
||||
.IsUnique();
|
||||
|
||||
builder.Property(x => x.FileExtension)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.FileSizeBytes)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.MimeType)
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
builder.Property(x => x.FileType)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.Category)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(100);
|
||||
|
||||
// ذخیرهسازی
|
||||
builder.Property(x => x.StorageProvider)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.StoragePath)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property(x => x.StorageUrl)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property(x => x.ThumbnailUrl)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
// متادیتا
|
||||
builder.Property(x => x.UploadedByUserId)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.UploadDate)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.HasIndex(x => x.Status);
|
||||
builder.HasIndex(x => x.UploadedByUserId);
|
||||
builder.HasIndex(x => x.Category);
|
||||
|
||||
// اطلاعات تصویر
|
||||
builder.Property(x => x.ImageWidth);
|
||||
builder.Property(x => x.ImageHeight);
|
||||
|
||||
// اطلاعات صوت/ویدیو
|
||||
builder.Property(x => x.DurationSeconds);
|
||||
|
||||
// امنیت
|
||||
builder.Property(x => x.VirusScanDate);
|
||||
builder.Property(x => x.IsVirusScanPassed);
|
||||
builder.Property(x => x.VirusScanResult)
|
||||
.HasMaxLength(500);
|
||||
|
||||
// Soft Delete
|
||||
builder.Property(x => x.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.DeletedDate);
|
||||
builder.Property(x => x.DeletedByUserId);
|
||||
|
||||
builder.HasIndex(x => x.IsDeleted);
|
||||
|
||||
// Reference Tracking
|
||||
builder.Property(x => x.ReferenceEntityType)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(x => x.ReferenceEntityId)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.HasIndex(x => new { x.ReferenceEntityType, x.ReferenceEntityId });
|
||||
|
||||
// Audit
|
||||
builder.Property(x => x.CreationDate)
|
||||
.IsRequired();
|
||||
|
||||
// Query Filter - فایلهای حذف نشده
|
||||
builder.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories.FileManagement;
|
||||
|
||||
public class UploadedFileRepository : IUploadedFileRepository
|
||||
{
|
||||
private readonly ProgramManagerDbContext _context;
|
||||
|
||||
public UploadedFileRepository(ProgramManagerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<UploadedFile?> GetByIdAsync(Guid fileId)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.FirstOrDefaultAsync(x => x.Id == fileId);
|
||||
}
|
||||
|
||||
public async Task<UploadedFile?> GetByUniqueFileNameAsync(string uniqueFileName)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.FirstOrDefaultAsync(x => x.UniqueFileName == uniqueFileName);
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> GetUserFilesAsync(long userId, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.UploadedByUserId == userId)
|
||||
.OrderByDescending(x => x.UploadDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> GetByCategoryAsync(FileCategory category, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.Category == category)
|
||||
.OrderByDescending(x => x.UploadDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> GetByStatusAsync(FileStatus status, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.Status == status)
|
||||
.OrderByDescending(x => x.UploadDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> GetByReferenceAsync(string entityType, string entityId)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.ReferenceEntityType == entityType && x.ReferenceEntityId == entityId)
|
||||
.OrderByDescending(x => x.UploadDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> SearchByNameAsync(string searchTerm, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.OriginalFileName.Contains(searchTerm))
|
||||
.OrderByDescending(x => x.UploadDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetUserFilesCountAsync(long userId)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.CountAsync(x => x.UploadedByUserId == userId);
|
||||
}
|
||||
|
||||
public async Task<long> GetUserTotalFileSizeAsync(long userId)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.Where(x => x.UploadedByUserId == userId)
|
||||
.SumAsync(x => x.FileSizeBytes);
|
||||
}
|
||||
|
||||
public async Task<List<UploadedFile>> GetExpiredFilesAsync(DateTime olderThan)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.IgnoreQueryFilters() // Include deleted files
|
||||
.Where(x => x.IsDeleted && x.DeletedDate < olderThan)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<UploadedFile> AddAsync(UploadedFile file)
|
||||
{
|
||||
await _context.UploadedFiles.AddAsync(file);
|
||||
return file;
|
||||
}
|
||||
|
||||
public Task UpdateAsync(UploadedFile file)
|
||||
{
|
||||
_context.UploadedFiles.Update(file);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteAsync(UploadedFile file)
|
||||
{
|
||||
_context.UploadedFiles.Remove(file);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<int> SaveChangesAsync()
|
||||
{
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(Guid fileId)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.AnyAsync(x => x.Id == fileId);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByUniqueFileNameAsync(string uniqueFileName)
|
||||
{
|
||||
return await _context.UploadedFiles
|
||||
.AnyAsync(x => x.UniqueFileName == uniqueFileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Repositories.TaskChat;
|
||||
|
||||
public class TaskChatMessageRepository : ITaskChatMessageRepository
|
||||
{
|
||||
private readonly ProgramManagerDbContext _context;
|
||||
|
||||
public TaskChatMessageRepository(ProgramManagerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<TaskChatMessage?> GetByIdAsync(Guid messageId)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.FirstOrDefaultAsync(x => x.Id == messageId);
|
||||
}
|
||||
|
||||
public async Task<List<TaskChatMessage>> GetTaskMessagesAsync(Guid taskId, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId)
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.OrderBy(x => x.CreationDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetTaskMessageCountAsync(Guid taskId)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.CountAsync(x => x.TaskId == taskId);
|
||||
}
|
||||
|
||||
public async Task<List<TaskChatMessage>> GetPinnedMessagesAsync(Guid taskId)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId && x.IsPinned)
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.OrderByDescending(x => x.PinnedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<TaskChatMessage?> GetLastMessageAsync(Guid taskId)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId)
|
||||
.OrderByDescending(x => x.CreationDate)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TaskChatMessage>> SearchMessagesAsync(Guid taskId, string searchText, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId &&
|
||||
x.TextContent != null &&
|
||||
x.TextContent.Contains(searchText))
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.OrderByDescending(x => x.CreationDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TaskChatMessage>> GetUserMessagesAsync(Guid taskId, long userId, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId && x.SenderUserId == userId)
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.OrderByDescending(x => x.CreationDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TaskChatMessage>> GetMediaMessagesAsync(Guid taskId, int pageNumber, int pageSize)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.Where(x => x.TaskId == taskId && x.FileId != null)
|
||||
.Include(x => x.ReplyToMessage)
|
||||
.OrderByDescending(x => x.CreationDate)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<TaskChatMessage> AddAsync(TaskChatMessage message)
|
||||
{
|
||||
await _context.TaskChatMessages.AddAsync(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
public Task UpdateAsync(TaskChatMessage message)
|
||||
{
|
||||
_context.TaskChatMessages.Update(message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DeleteAsync(TaskChatMessage message)
|
||||
{
|
||||
_context.TaskChatMessages.Remove(message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<int> SaveChangesAsync()
|
||||
{
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(Guid messageId)
|
||||
{
|
||||
return await _context.TaskChatMessages
|
||||
.AnyAsync(x => x.Id == messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس ذخیرهسازی فایل در سیستم فایل محلی
|
||||
/// </summary>
|
||||
public class LocalFileStorageService : IFileStorageService
|
||||
{
|
||||
private readonly string _uploadBasePath;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public LocalFileStorageService(IConfiguration configuration,
|
||||
IHttpContextAccessor httpContextAccessor, IHostEnvironment env)
|
||||
{
|
||||
// محاسبه مسیر پایه: اگر env نبود، از مسیر فعلی استفاده کن
|
||||
var contentRoot = env.ContentRootPath;
|
||||
_uploadBasePath = Path.Combine(contentRoot, "Storage");
|
||||
// Base URL برای دسترسی به فایلها
|
||||
var request = httpContextAccessor.HttpContext?.Request;
|
||||
if (request != null)
|
||||
{
|
||||
_baseUrl = $"{request.Scheme}://{request.Host}/uploads";
|
||||
}
|
||||
else
|
||||
{
|
||||
_baseUrl = configuration["FileStorage:BaseUrl"] ?? "http://localhost:5000/uploads";
|
||||
}
|
||||
|
||||
// ایجاد پوشه اگر وجود نداشت
|
||||
if (!Directory.Exists(_uploadBasePath))
|
||||
{
|
||||
Directory.CreateDirectory(_uploadBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string StoragePath, string StorageUrl)> UploadAsync(
|
||||
Stream fileStream,
|
||||
string uniqueFileName,
|
||||
string category)
|
||||
{
|
||||
// ایجاد پوشه دستهبندی (مثلاً: Uploads/TaskChatMessage)
|
||||
var categoryPath = Path.Combine(_uploadBasePath, category);
|
||||
if (!Directory.Exists(categoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(categoryPath);
|
||||
}
|
||||
|
||||
// ایجاد زیرپوشه بر اساس تاریخ (مثلاً: Uploads/TaskChatMessage/2026/01)
|
||||
var datePath = Path.Combine(categoryPath, DateTime.Now.Year.ToString(),
|
||||
DateTime.Now.Month.ToString("00"));
|
||||
if (!Directory.Exists(datePath))
|
||||
{
|
||||
Directory.CreateDirectory(datePath);
|
||||
}
|
||||
|
||||
// مسیر کامل فایل
|
||||
var storagePath = Path.Combine(datePath, uniqueFileName);
|
||||
|
||||
// ذخیره فایل
|
||||
await using var fileStreamOutput = new FileStream(storagePath, FileMode.Create, FileAccess.Write);
|
||||
await fileStream.CopyToAsync(fileStreamOutput);
|
||||
|
||||
// URL فایل
|
||||
var relativePath = Path.GetRelativePath(_uploadBasePath, storagePath)
|
||||
.Replace("\\", "/");
|
||||
var storageUrl = $"{_baseUrl}/{relativePath}";
|
||||
|
||||
return (storagePath, storageUrl);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(string storagePath)
|
||||
{
|
||||
if (File.Exists(storagePath))
|
||||
{
|
||||
File.Delete(storagePath);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<Stream?> GetFileStreamAsync(string storagePath)
|
||||
{
|
||||
if (!File.Exists(storagePath))
|
||||
{
|
||||
return Task.FromResult<Stream?>(null);
|
||||
}
|
||||
|
||||
var stream = new FileStream(storagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return Task.FromResult<Stream?>(stream);
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(string storagePath)
|
||||
{
|
||||
return Task.FromResult(File.Exists(storagePath));
|
||||
}
|
||||
|
||||
public string GetFileUrl(string storagePath)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(_uploadBasePath, storagePath)
|
||||
.Replace("\\", "/");
|
||||
return $"{_baseUrl}/{relativePath}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تولید thumbnail با استفاده از ImageSharp
|
||||
/// </summary>
|
||||
public class ThumbnailGeneratorService : IThumbnailGeneratorService
|
||||
{
|
||||
private readonly string _thumbnailBasePath;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public ThumbnailGeneratorService(IConfiguration configuration)
|
||||
{
|
||||
_thumbnailBasePath = configuration["FileStorage:ThumbnailPath"]
|
||||
?? Path.Combine(Directory.GetCurrentDirectory(), "Uploads", "Thumbnails");
|
||||
|
||||
_baseUrl = configuration["FileStorage:BaseUrl"] ?? "http://localhost:5000/uploads";
|
||||
|
||||
if (!Directory.Exists(_thumbnailBasePath))
|
||||
{
|
||||
Directory.CreateDirectory(_thumbnailBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateImageThumbnailAsync(
|
||||
string imagePath,
|
||||
int width = 200,
|
||||
int height = 200)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(imagePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// بارگذاری تصویر
|
||||
using var image = await Image.LoadAsync(imagePath);
|
||||
|
||||
// Resize با حفظ نسبت
|
||||
image.Mutate(x => x.Resize(new ResizeOptions
|
||||
{
|
||||
Size = new Size(width, height),
|
||||
Mode = ResizeMode.Max
|
||||
}));
|
||||
|
||||
// نام فایل thumbnail
|
||||
var fileName = Path.GetFileNameWithoutExtension(imagePath);
|
||||
var extension = Path.GetExtension(imagePath);
|
||||
var thumbnailFileName = $"{fileName}_thumb{extension}";
|
||||
|
||||
// مسیر ذخیره thumbnail
|
||||
var thumbnailPath = Path.Combine(_thumbnailBasePath, thumbnailFileName);
|
||||
|
||||
// ذخیره thumbnail با کیفیت 80
|
||||
await image.SaveAsync(thumbnailPath, new JpegEncoder { Quality = 80 });
|
||||
|
||||
// URL thumbnail
|
||||
var thumbnailUrl = $"{_baseUrl}/thumbnails/{thumbnailFileName}";
|
||||
|
||||
return (thumbnailPath, thumbnailUrl);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// در صورت خطا null برمیگردانیم
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateVideoThumbnailAsync(string videoPath)
|
||||
{
|
||||
// TODO: برای Video thumbnail باید از FFmpeg استفاده کنیم
|
||||
// فعلاً یک placeholder image برمیگردانیم
|
||||
await Task.CompletedTask;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task DeleteThumbnailAsync(string thumbnailPath)
|
||||
{
|
||||
if (File.Exists(thumbnailPath))
|
||||
{
|
||||
File.Delete(thumbnailPath);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<(int Width, int Height)?> GetImageDimensionsAsync(string imagePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(imagePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var imageInfo = await Image.IdentifyAsync(imagePath);
|
||||
return (imageInfo.Width, imageInfo.Height);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user