From 2d43e53b58eca71226eff1c7f4d3f65c63102016 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 21:01:32 +0330 Subject: [PATCH 001/106] add model s3Configure --- Refhub/Models/AppSetting/S3Configuration.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Refhub/Models/AppSetting/S3Configuration.cs diff --git a/Refhub/Models/AppSetting/S3Configuration.cs b/Refhub/Models/AppSetting/S3Configuration.cs new file mode 100644 index 00000000..67e45fbc --- /dev/null +++ b/Refhub/Models/AppSetting/S3Configuration.cs @@ -0,0 +1,11 @@ +namespace Refhub.Models.AppSetting +{ + public class S3Configuration + { + public string Region { get; set; } + public string AccessKey { get; set; } + public string SecretKey { get; set; } + public string BucketName { get; set; } + public string ServiceURL { get; set; } + } +} From 6738d8d3f43e78e68c6d4d960ad2e7077ebdd14d Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 21:01:46 +0330 Subject: [PATCH 002/106] bind appsetting to model s3 --- .../BindAppSettingToModelExtentionMethod.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs diff --git a/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs new file mode 100644 index 00000000..b3597e5f --- /dev/null +++ b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs @@ -0,0 +1,15 @@ +using Refhub.Models.AppSetting; +using Refhub.Service.Implement; +using Refhub.Service.Interface; + +namespace Refhub.Tools.ExtentionMethod; + +public static class BindAppSettingToModelExtentionMethod +{ + public static WebApplicationBuilder BindS3Model(this WebApplicationBuilder builder) + { + builder.Services.Configure( + builder.Configuration.GetSection("AWS:S3")); + return builder; + } +} From db7330591a5c36bc09ceb323e22d1898bcba1590 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 21:02:00 +0330 Subject: [PATCH 003/106] add configure s3 to appsetting --- Refhub/appsettings.Development.json | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Refhub/appsettings.Development.json b/Refhub/appsettings.Development.json index 8967cd3e..244953ea 100644 --- a/Refhub/appsettings.Development.json +++ b/Refhub/appsettings.Development.json @@ -1,11 +1,20 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "AWS": { + "S3": { + "Region": "s3.ir-thr-at1.arvanstorage.ir", + "AccessKey": "b00b79c3-6201-4d3b-9fde-62053d491ba3", + "SecretKey": "e6d3f6d22238420f66d60436ad8c16cb298e5410a66fb7d60081d90b673a06c7", + "BucketName": "test12333555", + "ServiceURL": "https://s3.ir-thr-at1.arvanstorage.ir/" + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Data Source=.;Initial Catalog=RefHubDB;Integrated Security=True;Multiple Active Result Sets=True;Trust Server Certificate=True" } - }, - "ConnectionStrings": { - "DefaultConnection": "Data Source=.;Initial Catalog=RefHubDB;Integrated Security=True;Multiple Active Result Sets=True;Trust Server Certificate=True" - } } From 8592baa5bcac944df6cf502ad7f1dc2c1c17ab93 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 21:02:18 +0330 Subject: [PATCH 004/106] use bind model to appsetting --- Refhub/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Refhub/Program.cs b/Refhub/Program.cs index f8b99052..2612b344 100644 --- a/Refhub/Program.cs +++ b/Refhub/Program.cs @@ -22,6 +22,7 @@ public static void Main(string[] args) #region CustomExtentionMethod + builder.BindS3Model(); builder.Services.AddCustomService(); builder.Services.ConfigureContext(builder.Configuration); builder.Services.ConfigureCookie(); From be0c36685c418761aadcdf765b79eb1d44a499b1 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:24:05 +0330 Subject: [PATCH 005/106] add migration to update Slug column type and create unique index on Books table --- .../20250611081248_fix-mig.Designer.cs | 541 ++++++++++++++++++ Refhub/Migrations/20250611081248_fix-mig.cs | 44 ++ .../Migrations/AppDbContextModelSnapshot.cs | 7 +- 3 files changed, 590 insertions(+), 2 deletions(-) create mode 100644 Refhub/Migrations/20250611081248_fix-mig.Designer.cs create mode 100644 Refhub/Migrations/20250611081248_fix-mig.cs diff --git a/Refhub/Migrations/20250611081248_fix-mig.Designer.cs b/Refhub/Migrations/20250611081248_fix-mig.Designer.cs new file mode 100644 index 00000000..1789c9ae --- /dev/null +++ b/Refhub/Migrations/20250611081248_fix-mig.Designer.cs @@ -0,0 +1,541 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Refhub.Data.Context; + +#nullable disable + +namespace Refhub.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250611081248_fix-mig")] + partial class fixmig + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Refhub.Data.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Refhub.Data.Models.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PageCount") + .HasColumnType("int"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(155) + .HasColumnType("nvarchar(155)"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookAuthor", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("AuthorId") + .HasColumnType("int"); + + b.HasKey("BookId", "AuthorId"); + + b.HasIndex("AuthorId"); + + b.ToTable("BookAuthors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookKeyword", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("KeywordId") + .HasColumnType("int"); + + b.HasKey("BookId", "KeywordId"); + + b.HasIndex("KeywordId"); + + b.ToTable("BookKeywords"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookRelation", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("RelatedBookId") + .HasColumnType("int"); + + b.HasKey("BookId", "RelatedBookId"); + + b.HasIndex("RelatedBookId"); + + b.ToTable("BookRelations"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Keyword", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Word") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Keywords"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.HasOne("Refhub.Data.Models.Category", "Category") + .WithMany("Books") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.ApplicationUser", "User") + .WithMany("Books") + .HasForeignKey("UserId"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookAuthor", b => + { + b.HasOne("Refhub.Data.Models.Author", "Author") + .WithMany("BookAuthors") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("BookAuthors") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Book"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookKeyword", b => + { + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("BookKeywords") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Keyword", "Keyword") + .WithMany("BookKeywords") + .HasForeignKey("KeywordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("Keyword"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookRelation", b => + { + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("RelatedTo") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Book", "RelatedBook") + .WithMany("RelatedFrom") + .HasForeignKey("RelatedBookId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("RelatedBook"); + }); + + modelBuilder.Entity("Refhub.Data.Models.ApplicationUser", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Author", b => + { + b.Navigation("BookAuthors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.Navigation("BookAuthors"); + + b.Navigation("BookKeywords"); + + b.Navigation("RelatedFrom"); + + b.Navigation("RelatedTo"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Category", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Keyword", b => + { + b.Navigation("BookKeywords"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Refhub/Migrations/20250611081248_fix-mig.cs b/Refhub/Migrations/20250611081248_fix-mig.cs new file mode 100644 index 00000000..801a9ba2 --- /dev/null +++ b/Refhub/Migrations/20250611081248_fix-mig.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refhub.Migrations +{ + /// + public partial class fixmig : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Slug", + table: "Books", + type: "nvarchar(450)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateIndex( + name: "IX_Books_Slug", + table: "Books", + column: "Slug", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Books_Slug", + table: "Books"); + + migrationBuilder.AlterColumn( + name: "Slug", + table: "Books", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(450)"); + } + } +} diff --git a/Refhub/Migrations/AppDbContextModelSnapshot.cs b/Refhub/Migrations/AppDbContextModelSnapshot.cs index 761c8139..e742b1fc 100644 --- a/Refhub/Migrations/AppDbContextModelSnapshot.cs +++ b/Refhub/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.15") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -266,7 +266,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Slug") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(450)"); b.Property("Title") .IsRequired() @@ -280,6 +280,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("CategoryId"); + b.HasIndex("Slug") + .IsUnique(); + b.HasIndex("UserId"); b.ToTable("Books"); From 76f59f1a35960d82e7cea7bd6cdb82b91f92972f Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:24:29 +0330 Subject: [PATCH 006/106] add duplicate PackageReference for AWSSDK.S3 in project file --- Refhub/Refhub.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index 91b998eb..caede9ab 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -12,6 +12,7 @@ + From ec8907c49734abb95251666a7ecd56d90ed26409 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:24:47 +0330 Subject: [PATCH 007/106] add DirectoryTypes enum to define file categories --- Refhub/Models/Enums/DirectoryTypes.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Refhub/Models/Enums/DirectoryTypes.cs diff --git a/Refhub/Models/Enums/DirectoryTypes.cs b/Refhub/Models/Enums/DirectoryTypes.cs new file mode 100644 index 00000000..9d6a5d1b --- /dev/null +++ b/Refhub/Models/Enums/DirectoryTypes.cs @@ -0,0 +1,13 @@ +namespace Refhub.Models.Enums +{ + public enum DirectoryTypes + { + None = 0, + Images, + Books, + Files, + + } + + +} From 5f27d8a34615142c8d182dbb5e6daf879e6c7d66 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:24:57 +0330 Subject: [PATCH 008/106] refactor FolderNameStatic to use GetDirectoryName method for directory name retrieval --- Refhub/Tools/Static/FolderNameStatic.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Refhub/Tools/Static/FolderNameStatic.cs b/Refhub/Tools/Static/FolderNameStatic.cs index e71d974f..bdd68c05 100644 --- a/Refhub/Tools/Static/FolderNameStatic.cs +++ b/Refhub/Tools/Static/FolderNameStatic.cs @@ -1,8 +1,18 @@ -namespace Refhub.Tools.Static; +using Refhub.Models.Enums; + +namespace Refhub.Tools.Static; public static class FolderNameStatic { - public static string GetDirectoryImages = "Images"; - public static string GetDirectoryBooks = "Books"; - public static string GetDirectoryFiles = "Files"; + public static string GetDirectoryName(DirectoryTypes folder) + { + return folder switch + { + DirectoryTypes.Images => nameof(DirectoryTypes.Images), + DirectoryTypes.Books => nameof(DirectoryTypes.Books), + DirectoryTypes.Files => nameof(DirectoryTypes.Files), + + _ => "Order" + }; + } } From d94d695cc4e65e7ff748d7445673c20748dabb96 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:07 +0330 Subject: [PATCH 009/106] fix: correct method name and parameter in IFileUploaderService interface --- Refhub/Service/Interface/IFileUploaderService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index 5f920873..f43639e4 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -2,6 +2,7 @@ public interface IFileUploaderService { - Task UpdloadFile(IFormFile file, string directoryName, string Type, string Name); - Task DeleteFile(string directoryName, string Type, string Name); + Task UploadFile(IFormFile file, string directoryName, string Type, string Name); + + Task DeleteFile(string realUrl); } From 52476d3f4a813109968784b16deb8ca384d61310 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:12 +0330 Subject: [PATCH 010/106] fix: correct method name from UpdloadFile to UploadFile in LocalFileUploaderService --- Refhub/Service/Implement/LocalFileUploaderService.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index f1a4aa78..3e3ecead 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -4,7 +4,7 @@ namespace Refhub.Service.Implement; public class LocalFileUploaderService : IFileUploaderService { - public async Task UpdloadFile(IFormFile file, string directoryName, string Type, string Name) + public async Task UploadFile(IFormFile file, string directoryName, string Type, string Name) { if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type))) { @@ -35,4 +35,9 @@ public async Task DeleteFile(string directoryName, string Type, string Name) } } + + public Task DeleteFile(string realUrl) + { + throw new NotImplementedException(); + } } From 7fe2a3a2e88cfa10ea29b95093bd85ce5fcb5806 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:16 +0330 Subject: [PATCH 011/106] feat: implement S3FileUploaderService for file upload and deletion using AWS S3 --- .../Implement/S3FileUploaderService.cs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Refhub/Service/Implement/S3FileUploaderService.cs diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs new file mode 100644 index 00000000..15c62988 --- /dev/null +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.Options; +using Refhub.Models.AppSetting; +using Refhub.Service.Interface; + +namespace Refhub.Service.Implement +{ + namespace S3_Sample.Service + { + using Amazon; + using Amazon.Runtime; + using Amazon.S3; + using Amazon.S3.Model; + using Amazon.S3.Transfer; + using Microsoft.Extensions.Configuration; + + + + + + public class S3FileUploaderService: IFileUploaderService + { + private readonly string _bucketName; + private readonly string _region; + private readonly IAmazonS3 _s3Client; + private readonly IOptions _s3Options; + + public S3FileUploaderService(IOptions s3Options) + { + this._s3Options = s3Options; + + var credentials = new BasicAWSCredentials(s3Options.Value.AccessKey, s3Options.Value.SecretKey); + + var config = new AmazonS3Config + { + RegionEndpoint = RegionEndpoint.USEast1, // منطقه ساختگی، چون ArvanRegion اختصاصی داره + ServiceURL = s3Options.Value.ServiceURL, + ForcePathStyle = true // بسیار مهم برای کار با آروان‌کلاد + }; + + _s3Client = new AmazonS3Client(credentials, config); + + _region = s3Options.Value.Region; + _bucketName = s3Options.Value.BucketName; + } + private string GenerateS3Url(string key) + { + // برای آروان کلاد: + return $"https://{_bucketName}.{_region}/{key}"; + } + + private string GetKey(string realUrl) + { + // برای آروان کلاد: + + return realUrl.Replace($"https://{_bucketName}.{_region}/",""); + } + public async Task UploadFile(IFormFile file, string directoryName, string type, string name) + { + var bucketName = _bucketName; + var key = $"{directoryName}/{type}/{name.Replace(" ","-")}{Path.GetExtension(file.FileName)}"; + + using var stream = file.OpenReadStream(); + var request = new PutObjectRequest + { + BucketName = bucketName, + Key = key, + InputStream = stream, + ContentType = file.ContentType, + + CannedACL = S3CannedACL.PublicRead + }; + + await _s3Client.PutObjectAsync(request); + return GenerateS3Url(key); + } + + + public async Task DeleteFile(string realUrl) + { + + var key = GetKey(realUrl); + + var request = new DeleteObjectRequest + { + BucketName = _bucketName, + Key = key + }; + + await _s3Client.DeleteObjectAsync(request); + } + } + } + +} From 4d5527bd0d67c206d56c96e34b879a64708410f8 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:28 +0330 Subject: [PATCH 012/106] fix: simplify ConvertForBookPathImage and ConvertForBookPathFile methods to return input directly --- Refhub/Tools/ExtentionMethod/PathExtionMethod.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs b/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs index 71b7cda7..f7c9ab9b 100644 --- a/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs +++ b/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs @@ -6,14 +6,10 @@ public static class PathImageExtionMethod { public static string ConvertForBookPathImage(this string imageName) { - return string.IsNullOrEmpty(imageName) - ? string.Empty - : $"/{FolderNameStatic.GetDirectoryFiles}/{FolderNameStatic.GetDirectoryImages}/{FolderNameStatic.GetDirectoryBooks}/{imageName}"; + return imageName; } public static string ConvertForBookPathFile(this string fileName) { - return string.IsNullOrEmpty(fileName) - ? string.Empty - : $"\\{FolderNameStatic.GetDirectoryFiles}\\{FolderNameStatic.GetDirectoryImages}\\{FolderNameStatic.GetDirectoryBooks}\\{fileName}"; + return fileName; } } From b7d6b9d149f578ebea99d679e40a4a056c39fa2f Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:53 +0330 Subject: [PATCH 013/106] fix: update IFileUploaderService to use S3FileUploaderService instead of LocalFileUploaderService --- Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs b/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs index f93cc45e..a6c34587 100644 --- a/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs +++ b/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs @@ -1,4 +1,5 @@ using Refhub.Service.Implement; +using Refhub.Service.Implement.S3_Sample.Service; using Refhub.Service.Interface; namespace Refhub.Tools.ExtentionMethod; @@ -14,7 +15,7 @@ public static IServiceCollection AddCustomService(this IServiceCollection collec collection.AddScoped(); collection.AddScoped(); - collection.AddScoped(); + collection.AddScoped(); return collection; } } From e53459a327947bbb55cc4461d871e3be748e11ed Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Wed, 11 Jun 2025 22:25:59 +0330 Subject: [PATCH 014/106] fix: correct method name from UpdloadFile to UploadFile and simplify file deletion logic in BookService --- Refhub/Service/Implement/BookService.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index f483b640..ae2f7731 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -5,6 +5,7 @@ using Refhub.Models.Books; using Refhub.Models.Category; using Refhub.Models.DTO; +using Refhub.Models.Enums; using Refhub.Service.Interface; using Refhub.Tools.Static; @@ -152,9 +153,9 @@ public async Task CreateBookAsync(CreateBookVM book, CancellationToken ct) { AuthorId = a }).ToList(); - - var filePath = await uploaderService.UpdloadFile(book.File, FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.Slug); - var imagePath = await uploaderService.UpdloadFile(book.Image, FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.Slug); + + var filePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); + var imagePath = await uploaderService.UploadFile(book.Image, FolderNameStatic.GetDirectoryName(DirectoryTypes.Images), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); if (string.IsNullOrWhiteSpace(filePath) || string.IsNullOrWhiteSpace(imagePath)) { @@ -208,20 +209,20 @@ public async Task UpdateBookAsync(UpdateBookVM book, CancellationToken ct) { if (!string.IsNullOrWhiteSpace(existingBook.FilePath)) { - await uploaderService.DeleteFile(FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, existingBook.FilePath); + await uploaderService.DeleteFile( existingBook.FilePath); } - existingBook.FilePath = await uploaderService.UpdloadFile(book.File, FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.Slug); + existingBook.FilePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); } if (book.Image != null) { if (!string.IsNullOrWhiteSpace(existingBook.ImagePath)) { - await uploaderService.DeleteFile(FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, existingBook.ImagePath); + await uploaderService.DeleteFile(existingBook.ImagePath); } - existingBook.ImagePath = await uploaderService.UpdloadFile(book.Image, FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.Slug); + existingBook.ImagePath = await uploaderService.UploadFile(book.Image, FolderNameStatic.GetDirectoryName(DirectoryTypes.Images), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); } // حذف نویسنده‌های قبلی و اضافه کردن جدید @@ -260,12 +261,12 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) if (!string.IsNullOrWhiteSpace(book.ImagePath)) { - await uploaderService.DeleteFile(FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.ImagePath); + await uploaderService.DeleteFile( book.ImagePath); } if (!string.IsNullOrWhiteSpace(book.FilePath)) { - await uploaderService.DeleteFile(FolderNameStatic.GetDirectoryBooks, FolderNameStatic.GetDirectoryImages, book.FilePath); + await uploaderService.DeleteFile( book.FilePath); } context.Books.Remove(book); From 9102cb4578744fb7e64e6f8c38704237e7e1f163 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 15:22:47 +0330 Subject: [PATCH 015/106] Fix formatting in Refhub.csproj Updated the `` line to include a leading space for consistency. No functional changes were made to the project structure. --- Refhub/Refhub.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index caede9ab..6ce94f55 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -1,4 +1,4 @@ - + net9.0 From 5a7367fc01bcdd595c96e82271612b5cd0a3b8cd Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 15:23:02 +0330 Subject: [PATCH 016/106] Enhance BookController for S3 file downloads - Added IFileUploaderService dependency to BookController. - Updated DownloadFile method to use file name in route. - Implemented error handling for file download failures. - Added [Authorize] attribute to DownloadFile method. - Removed unused local file path handling code. --- Refhub/Controllers/BookController.cs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index a07a8ce3..5c8903c1 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Refhub.Service.Implement.S3_Sample.Service; using Refhub.Service.Interface; namespace Refhub.Controllers; -public class BookController(IBookService bookService) : Controller +public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService) : Controller { [HttpGet("BookDetails/{slug}")] public async Task BookDetails(string slug, CancellationToken ct) @@ -19,18 +20,23 @@ public async Task BookDetails(string slug, CancellationToken ct) return bookDetails == null ? NotFound() : View(bookDetails); } + + [Authorize] - [HttpGet] - public async Task DownloadFile(string filePath, CancellationToken ct) + [HttpGet("download/{fileName}")] + public async Task DownloadFile(string fileName, CancellationToken ct) { - filePath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot{filePath}"); - if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath)) + try { - return NotFound(); - } + var stream = await _s3FileUploaderService.DownloadFileAsync(fileName); + var contentType = "application/octet-stream"; // یا محتوای واقعی بر اساس پسوند - var fileBytes = await System.IO.File.ReadAllBytesAsync(filePath, ct); - return File(fileBytes, "application/octet-stream", Path.GetFileName(filePath)); + return File(stream, contentType, fileName); + } + catch (Exception ex) + { + return NotFound($"خطا در دانلود فایل: {ex.Message}"); + } } private readonly int _pageSize = 3; @@ -41,4 +47,6 @@ public async Task List(string searchText, string authorFilter, st return View(viewModel); } + + } From 4e68b7f3b95e937f8a9e8b29c898e95a344ba4d9 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 15:23:15 +0330 Subject: [PATCH 017/106] Remove associated BookAuthors on Book deletion Updated the `BookService` class to remove related `BookAuthors` when a `Book` is deleted. This change ensures referential integrity by preventing orphaned `BookAuthors` in the database. --- Refhub/Service/Implement/BookService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index ae2f7731..281f24af 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -268,6 +268,8 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) { await uploaderService.DeleteFile( book.FilePath); } + context.BookAuthors.RemoveRange(book.BookAuthors); + context.Books.Remove(book); await context.SaveChangesAsync(ct); From 2fa8835a6fbc4770dec10fece5a82e6f8aaddf4f Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 15:23:27 +0330 Subject: [PATCH 018/106] Add DownloadFileAsync method to S3FileUploaderService This commit introduces a new asynchronous method `DownloadFileAsync` in the `S3FileUploaderService` class. The method allows for downloading files from an S3 bucket using a specified `fileName`. It handles the creation of a `GetObjectRequest`, retrieves the file, and returns it as a `MemoryStream`. Additionally, it includes error handling for potential `AmazonS3Exception` cases, providing a clear error message if the file is not found or the key is incorrect. --- .../Implement/S3FileUploaderService.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 15c62988..d66385ca 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -88,6 +88,29 @@ public async Task DeleteFile(string realUrl) await _s3Client.DeleteObjectAsync(request); } + + public async Task DownloadFileAsync(string fileName) + { + var request = new GetObjectRequest + { + BucketName = _bucketName, + Key = fileName // مثلاً: "images/profile.jpg" + }; + + try + { + using var response = await _s3Client.GetObjectAsync(request); + var memoryStream = new MemoryStream(); + await response.ResponseStream.CopyToAsync(memoryStream); + memoryStream.Position = 0; // مهم! برای اینکه موقع Return از ابتدا خونده بشه + return memoryStream; + } + catch (AmazonS3Exception ex) + { + // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود + throw new Exception($"خطا در دانلود فایل از S3: {ex.Message}", ex); + } + } } } From e4405b175f685133838ca09dd14c847d97490e3f Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 15:23:37 +0330 Subject: [PATCH 019/106] Add DownloadFileAsync method to IFileUploaderService Introduced a new method `DownloadFileAsync` in the `IFileUploaderService` interface for asynchronous file download functionality, accepting a `string fileName` parameter and returning a `Task`. --- Refhub/Service/Interface/IFileUploaderService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index f43639e4..eccfe732 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -5,4 +5,6 @@ public interface IFileUploaderService Task UploadFile(IFormFile file, string directoryName, string Type, string Name); Task DeleteFile(string realUrl); + Task DownloadFileAsync(string fileName); + } From dc3a765320e48a70d762b05ba58808864536d21f Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 16:05:01 +0330 Subject: [PATCH 020/106] Add UserSecretsId and secure AWS credentials - Added `` to `Refhub.csproj` for user secrets management. - Updated `appsettings.Development.json` to remove hardcoded AWS `AccessKey` and `SecretKey`, replacing them with empty strings for improved security. --- Refhub/Refhub.csproj | 3 ++- Refhub/appsettings.Development.json | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index 6ce94f55..53ffe1d0 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -3,7 +3,8 @@ net9.0 enable - enable + enable + c6b529e8-deca-497c-84d4-0bddb8042e65 diff --git a/Refhub/appsettings.Development.json b/Refhub/appsettings.Development.json index 244953ea..2d8a7cc0 100644 --- a/Refhub/appsettings.Development.json +++ b/Refhub/appsettings.Development.json @@ -2,8 +2,8 @@ "AWS": { "S3": { "Region": "s3.ir-thr-at1.arvanstorage.ir", - "AccessKey": "b00b79c3-6201-4d3b-9fde-62053d491ba3", - "SecretKey": "e6d3f6d22238420f66d60436ad8c16cb298e5410a66fb7d60081d90b673a06c7", + "AccessKey": "", + "SecretKey": "", "BucketName": "test12333555", "ServiceURL": "https://s3.ir-thr-at1.arvanstorage.ir/" } From 6c8d855877b63d7b6acd0325695d80afdce50c81 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 20:18:07 +0330 Subject: [PATCH 021/106] Add cascade delete to BookAuthor foreign keys Implement cascade delete behavior for BookId and AuthorId in BookAuthorConfiguration to ensure related entries are automatically removed when a Book or Author is deleted. --- Refhub/Data/Configuration/BookAuthorConfiguration.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Refhub/Data/Configuration/BookAuthorConfiguration.cs b/Refhub/Data/Configuration/BookAuthorConfiguration.cs index 380f0178..5e1c8bb8 100644 --- a/Refhub/Data/Configuration/BookAuthorConfiguration.cs +++ b/Refhub/Data/Configuration/BookAuthorConfiguration.cs @@ -13,10 +13,12 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(ba => ba.Book) .WithMany(b => b.BookAuthors) - .HasForeignKey(ba => ba.BookId); + .HasForeignKey(ba => ba.BookId) + .OnDelete(DeleteBehavior.Cascade); builder.HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) - .HasForeignKey(ba => ba.AuthorId); + .HasForeignKey(ba => ba.AuthorId) + .OnDelete(DeleteBehavior.Cascade); } } From 8f379561be86144e7a6d6bee35672e2fae603026 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 20:18:19 +0330 Subject: [PATCH 022/106] Enhance BookService with file uploads and deletion logic Updated the `BookService` class to support file uploads for both book files and images using `IFileUploaderService`. Added validation for upload paths. Improved the book deletion method to load and remove related `BookAuthors` and `BookRelations`, ensuring all associated entities are handled correctly during deletion. --- Refhub/Service/Implement/BookService.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index 281f24af..c5ab2785 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -153,7 +153,7 @@ public async Task CreateBookAsync(CreateBookVM book, CancellationToken ct) { AuthorId = a }).ToList(); - + var filePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); var imagePath = await uploaderService.UploadFile(book.Image, FolderNameStatic.GetDirectoryName(DirectoryTypes.Images), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); @@ -209,7 +209,7 @@ public async Task UpdateBookAsync(UpdateBookVM book, CancellationToken ct) { if (!string.IsNullOrWhiteSpace(existingBook.FilePath)) { - await uploaderService.DeleteFile( existingBook.FilePath); + await uploaderService.DeleteFile(existingBook.FilePath); } existingBook.FilePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); @@ -253,7 +253,9 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) { try { - var book = await context.Books.FirstOrDefaultAsync(b => b.Id == bookId, ct); + var book = await context.Books + .Include(b => b.BookAuthors) + .FirstOrDefaultAsync(b => b.Id == bookId, ct); if (book == null) { return false; @@ -261,15 +263,19 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) if (!string.IsNullOrWhiteSpace(book.ImagePath)) { - await uploaderService.DeleteFile( book.ImagePath); + await uploaderService.DeleteFile(book.ImagePath); } if (!string.IsNullOrWhiteSpace(book.FilePath)) { - await uploaderService.DeleteFile( book.FilePath); + await uploaderService.DeleteFile(book.FilePath); } - context.BookAuthors.RemoveRange(book.BookAuthors); + var relations = await context.BookRelations + .Where(br => br.BookId == bookId || br.RelatedBookId == bookId) + .ToListAsync(ct); + if (relations.Any()) + context.BookRelations.RemoveRange(relations); context.Books.Remove(book); await context.SaveChangesAsync(ct); From b9f31aa7be9c52957d46a24ac63c840197dfbf08 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Thu, 12 Jun 2025 20:18:27 +0330 Subject: [PATCH 023/106] Update S3 URL generation logic in S3FileUploaderService Modified the `GenerateS3Url` method to change the URL format from `https://{_bucketName}.{_region}/{key}` to `https://{_region}/{_bucketName}.{_region}/{key}`. This adjustment accommodates a new URL structure for accessing resources, likely due to a change in cloud provider or service configuration. --- Refhub/Service/Implement/S3FileUploaderService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index d66385ca..f898b5ca 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -45,7 +45,7 @@ public S3FileUploaderService(IOptions s3Options) private string GenerateS3Url(string key) { // برای آروان کلاد: - return $"https://{_bucketName}.{_region}/{key}"; + return $"https://{_region}/{_bucketName}.{_region}/{key}"; } private string GetKey(string realUrl) From a5594961e35e3a226e53d99efa0643c13ce7e154 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 01:18:38 +0330 Subject: [PATCH 024/106] Add S3 support and improve error handling in BookController - Updated using directives to include Amazon.S3 for S3 interactions. - Modified BookController constructor to include ILogger for logging. - Simplified content type assignment in DownloadFile method. - Added specific catch block for AmazonS3Exception with logging. - Enhanced generic exception handling for better user feedback. --- Refhub/Controllers/BookController.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 5c8903c1..27b5905e 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Authorization; +using Amazon.S3; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Refhub.Service.Implement.S3_Sample.Service; using Refhub.Service.Interface; namespace Refhub.Controllers; -public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService) : Controller +public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService,ILogger _logger) : Controller { [HttpGet("BookDetails/{slug}")] public async Task BookDetails(string slug, CancellationToken ct) @@ -29,13 +30,17 @@ public async Task DownloadFile(string fileName, CancellationToken try { var stream = await _s3FileUploaderService.DownloadFileAsync(fileName); - var contentType = "application/octet-stream"; // یا محتوای واقعی بر اساس پسوند - - return File(stream, contentType, fileName); + return File(stream, "application/octet-stream", fileName); + } + catch (AmazonS3Exception s3Ex) + { + _logger.LogError(s3Ex, "خطا در دانلود فایل از S3: {Message}", s3Ex.Message); + return NotFound("خطا در دریافت فایل. لطفاً بعداً تلاش کنید."); } catch (Exception ex) { - return NotFound($"خطا در دانلود فایل: {ex.Message}"); + _logger.LogError(ex, "خطای پیش‌بینی‌نشده هنگام دانلود فایل."); + return StatusCode(500, "خطای غیرمنتظره‌ای رخ داده است. لطفاً با پشتیبانی تماس بگیرید."); } } From f82eb34c2a81cfb9d22e9ef91b2d11f398d4e48e Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 01:18:58 +0330 Subject: [PATCH 025/106] Comment out Book entity configuration in BookAuthor This commit removes the configuration for the `Book` entity's relationship with the `BookAuthor` entity in `BookAuthorConfiguration.cs`. The foreign key relationship and delete behavior for the `Book` entity have been commented out, while the configuration for the `Author` entity remains intact. --- Refhub/Data/Configuration/BookAuthorConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Refhub/Data/Configuration/BookAuthorConfiguration.cs b/Refhub/Data/Configuration/BookAuthorConfiguration.cs index 5e1c8bb8..d7214dea 100644 --- a/Refhub/Data/Configuration/BookAuthorConfiguration.cs +++ b/Refhub/Data/Configuration/BookAuthorConfiguration.cs @@ -11,10 +11,10 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(ba => new { ba.BookId, ba.AuthorId }); - builder.HasOne(ba => ba.Book) - .WithMany(b => b.BookAuthors) - .HasForeignKey(ba => ba.BookId) - .OnDelete(DeleteBehavior.Cascade); + //builder.HasOne(ba => ba.Book) + //.WithMany(b => b.BookAuthors) + //.HasForeignKey(ba => ba.BookId) + //.OnDelete(DeleteBehavior.Cascade); builder.HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) From 637b8b1cd964361658c323e5dfd74d416ec7519a Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 01:19:47 +0330 Subject: [PATCH 026/106] Refactor S3FileUploaderService URL handling Updated S3FileUploaderService to improve URL generation and key extraction. - Adjusted class definition formatting for consistency. - Modified GenerateS3Url to use ServiceURL from _s3Options. - Added GetKey method for extracting keys from URLs. - Reformatted UploadFile key generation for clarity. - Updated DeleteFile to utilize the new GetKey method. --- .../Service/Implement/S3FileUploaderService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index f898b5ca..83b368f4 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -10,14 +10,12 @@ namespace S3_Sample.Service using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; - using Amazon.S3.Transfer; - using Microsoft.Extensions.Configuration; - public class S3FileUploaderService: IFileUploaderService + public class S3FileUploaderService : IFileUploaderService { private readonly string _bucketName; private readonly string _region; @@ -45,19 +43,21 @@ public S3FileUploaderService(IOptions s3Options) private string GenerateS3Url(string key) { // برای آروان کلاد: - return $"https://{_region}/{_bucketName}.{_region}/{key}"; + return $"{_s3Options.Value.ServiceURL}/{_bucketName}/{key}"; } + private string GetKey(string realUrl) { // برای آروان کلاد: - - return realUrl.Replace($"https://{_bucketName}.{_region}/",""); + + return realUrl.Replace($"{_s3Options.Value.ServiceURL}/{_bucketName}/", ""); + } public async Task UploadFile(IFormFile file, string directoryName, string type, string name) { var bucketName = _bucketName; - var key = $"{directoryName}/{type}/{name.Replace(" ","-")}{Path.GetExtension(file.FileName)}"; + var key = $"{directoryName}/{type}/{name.Replace(" ", "-")}{Path.GetExtension(file.FileName)}"; using var stream = file.OpenReadStream(); var request = new PutObjectRequest @@ -77,7 +77,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin public async Task DeleteFile(string realUrl) { - + var key = GetKey(realUrl); var request = new DeleteObjectRequest From 59810903f3fedaa849103ce9de774aaf6e0ed085 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 11:39:18 +0330 Subject: [PATCH 027/106] Enhance file download handling in BookController - Added Microsoft.AspNetCore.StaticFiles namespace for content type handling. - Updated DownloadFile method to include CancellationToken for better async control. - Implemented content type determination based on file extension, defaulting to "application/octet-stream" if unknown. - Maintained existing error logging for S3 download exceptions. --- Refhub/Controllers/BookController.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 27b5905e..9abc4925 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -1,6 +1,7 @@ using Amazon.S3; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; using Refhub.Service.Implement.S3_Sample.Service; using Refhub.Service.Interface; @@ -29,8 +30,17 @@ public async Task DownloadFile(string fileName, CancellationToken { try { - var stream = await _s3FileUploaderService.DownloadFileAsync(fileName); - return File(stream, "application/octet-stream", fileName); + // دریافت فایل از S3 + var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct); + + // تعیین نوع فایل با توجه به پسوند + var contentTypeProvider = new FileExtensionContentTypeProvider(); + if (!contentTypeProvider.TryGetContentType(fileName, out string contentType)) + { + contentType = "application/octet-stream"; // پیش‌فرض اگر پسوند ناشناس باشد + } + + return File(stream, contentType, fileName); } catch (AmazonS3Exception s3Ex) { From 8060e865221e4f72d547e3cbbabf78fb3d80c20b Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 11:39:47 +0330 Subject: [PATCH 028/106] Enhance LocalFileUploaderService functionality - Added `_rootPath` for improved file path management. - Updated `UploadFile` to use `_rootPath` and generate unique file names. - Implemented asynchronous file saving with `CopyToAsync`. - Refactored `DeleteFile` to accept a `realUrl` parameter. - Introduced `DownloadFileAsync` for asynchronous file downloads with error handling. Enhance LocalFileUploaderService functionality - Added `_rootPath` for improved file path management. - Updated `UploadFile` to use `_rootPath` and generate unique file names. - Implemented asynchronous file saving with `CopyToAsync`. - Simplified `DeleteFile` method to accept a single `realUrl` parameter. - Introduced `DownloadFileAsync` for handling file downloads. - Added comments in Persian for clarity on directory creation and file handling. --- .../Implement/LocalFileUploaderService.cs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index 3e3ecead..edad5d6e 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -4,40 +4,55 @@ namespace Refhub.Service.Implement; public class LocalFileUploaderService : IFileUploaderService { - public async Task UploadFile(IFormFile file, string directoryName, string Type, string Name) + private readonly string _rootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files"); + + public async Task UploadFile(IFormFile file, string directoryName, string type, string name) { - if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type))) - { - Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type)); - } + // ایجاد مسیر دایرکتوری نهایی + string targetDirectory = Path.Combine(_rootPath, type, directoryName); - if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type, directoryName))) + // ایجاد پوشه‌ها در صورت نبود + if (!Directory.Exists(targetDirectory)) { - Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type, directoryName)); + Directory.CreateDirectory(targetDirectory); } - Name = Name.Replace(" ", "-") + new Random().Next(1111, 9999); - string name = Name + Path.GetExtension(file.FileName); - string path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type, directoryName, name); - using (Stream stream = new FileStream(path, FileMode.Create)) + // تمیزسازی و ساخت نام یکتا + name = name.Replace(" ", "-") + "_" + Path.GetRandomFileName().Replace(".", ""); + string fileName = name + Path.GetExtension(file.FileName); + string fullPath = Path.Combine(targetDirectory, fileName); + + // ذخیره فایل به صورت async + using (Stream stream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) { - file.CopyTo(stream); + await file.CopyToAsync(stream); } - return name; + return fileName; } - public async Task DeleteFile(string directoryName, string Type, string Name) + public Task DeleteFile(string realUrl) { - if (File.Exists(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type, directoryName, Name))) + string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", realUrl.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); + + if (File.Exists(fullPath)) { - File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files", Type, directoryName, Name)); + File.Delete(fullPath); } + return Task.CompletedTask; } - public Task DeleteFile(string realUrl) + public Task DownloadFileAsync(string fileName) { - throw new NotImplementedException(); + string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); + + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException("فایل پیدا نشد", fileName); + } + + Stream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); + return Task.FromResult(fileStream); } } From 7658b5c643ebc58e75789eeaac4a33c8cbae5be7 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 11:40:00 +0330 Subject: [PATCH 029/106] Update IFileUploaderService: Modify DownloadFileAsync The `DownloadFileAsync` method now includes a `CancellationToken ct` parameter, updating its signature to `Task DownloadFileAsync(string fileName, CancellationToken ct)`. The previous version of the method has been removed from the interface. --- Refhub/Service/Interface/IFileUploaderService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index eccfe732..cff83570 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -5,6 +5,6 @@ public interface IFileUploaderService Task UploadFile(IFormFile file, string directoryName, string Type, string Name); Task DeleteFile(string realUrl); - Task DownloadFileAsync(string fileName); + Task DownloadFileAsync(string fileName,CancellationToken ct); } From 546c773fcc0c3262418b738fd9479827041a2a21 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Wed, 25 Jun 2025 11:40:18 +0330 Subject: [PATCH 030/106] Remove default case for unsupported directory types The switch expression in FolderNameStatic.cs has been updated to remove the default case that returned the string "Order" for unsupported directory types. It now throws an `ArgumentOutOfRangeException` with a descriptive message when an unsupported type is encountered. --- Refhub/Tools/Static/FolderNameStatic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Tools/Static/FolderNameStatic.cs b/Refhub/Tools/Static/FolderNameStatic.cs index bdd68c05..d7068ab5 100644 --- a/Refhub/Tools/Static/FolderNameStatic.cs +++ b/Refhub/Tools/Static/FolderNameStatic.cs @@ -12,7 +12,7 @@ public static string GetDirectoryName(DirectoryTypes folder) DirectoryTypes.Books => nameof(DirectoryTypes.Books), DirectoryTypes.Files => nameof(DirectoryTypes.Files), - _ => "Order" + _ => throw new ArgumentOutOfRangeException(nameof(folder), $"Unsupported directory type: {folder}") }; } } From 7b6aa837f27f2826e13fa2915e9e010e1cad4f02 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Sat, 28 Jun 2025 16:30:43 +0330 Subject: [PATCH 031/106] Update S3Configuration for immutability and validation Refactor the `S3Configuration` class to use C# 9.0's `init` accessor for properties, ensuring immutability after initialization. Add `[Required]` attributes to enforce mandatory fields, and change the `ServiceURL` property to be nullable. Include comments for clarity on the class's purpose related to AWS S3 configuration. --- Refhub/Models/AppSetting/S3Configuration.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Refhub/Models/AppSetting/S3Configuration.cs b/Refhub/Models/AppSetting/S3Configuration.cs index 67e45fbc..1f155976 100644 --- a/Refhub/Models/AppSetting/S3Configuration.cs +++ b/Refhub/Models/AppSetting/S3Configuration.cs @@ -1,11 +1,16 @@ -namespace Refhub.Models.AppSetting +using System.ComponentModel.DataAnnotations; +/// +/// Strongly-typed binding for the “AWS:S3” section. +/// Do NOT commit real keys – store them in UserSecrets / env-vars. +/// +namespace Refhub.Models.AppSetting { - public class S3Configuration + public sealed class S3Configuration { - public string Region { get; set; } - public string AccessKey { get; set; } - public string SecretKey { get; set; } - public string BucketName { get; set; } - public string ServiceURL { get; set; } + [Required] public string Region { get; init; } = default!; + [Required] public string AccessKey { get; init; } = default!; + [Required] public string SecretKey { get; init; } = default!; + [Required] public string BucketName { get; init; } = default!; + public string? ServiceURL { get; init; } } } From badbb166fa744e49b9f5fcb8d92d50398c9236d8 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Sat, 28 Jun 2025 16:30:56 +0330 Subject: [PATCH 032/106] Remove DownloadFileAsync method from IFileUploaderService The `DownloadFileAsync` method has been removed from the `IFileUploaderService` interface in `IFileUploaderService.cs`. This method previously facilitated asynchronous file downloads using a file name and a cancellation token. --- Refhub/Service/Interface/IFileUploaderService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index cff83570..b6cbd7a7 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -6,5 +6,4 @@ public interface IFileUploaderService Task DeleteFile(string realUrl); Task DownloadFileAsync(string fileName,CancellationToken ct); - } From 1011198f21c228bdd42ae281fc5a99ecbb46f405 Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Sat, 28 Jun 2025 16:31:06 +0330 Subject: [PATCH 033/106] Add cancellation support to DownloadFileAsync method - Introduced `using System.Threading;` for cancellation tokens. - Modified `DownloadFileAsync` to accept a `CancellationToken`. - Updated S3 file download and stream copy operations to support cancellation. --- Refhub/Service/Implement/S3FileUploaderService.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 83b368f4..b56bd270 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -10,10 +10,7 @@ namespace S3_Sample.Service using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; - - - - + using System.Threading; public class S3FileUploaderService : IFileUploaderService { @@ -89,7 +86,7 @@ public async Task DeleteFile(string realUrl) await _s3Client.DeleteObjectAsync(request); } - public async Task DownloadFileAsync(string fileName) + public async Task DownloadFileAsync(string fileName, CancellationToken ct) { var request = new GetObjectRequest { @@ -99,9 +96,9 @@ public async Task DownloadFileAsync(string fileName) try { - using var response = await _s3Client.GetObjectAsync(request); + using var response = await _s3Client.GetObjectAsync(request,ct); var memoryStream = new MemoryStream(); - await response.ResponseStream.CopyToAsync(memoryStream); + await response.ResponseStream.CopyToAsync(memoryStream,ct); memoryStream.Position = 0; // مهم! برای اینکه موقع Return از ابتدا خونده بشه return memoryStream; } @@ -111,6 +108,8 @@ public async Task DownloadFileAsync(string fileName) throw new Exception($"خطا در دانلود فایل از S3: {ex.Message}", ex); } } + + } } From a87123f6212ad04ccbb682d4f38357efea34681a Mon Sep 17 00:00:00 2001 From: mohammad mehdi khodabande Date: Sat, 28 Jun 2025 16:37:31 +0330 Subject: [PATCH 034/106] Enhance DownloadFileAsync with CancellationToken support Updated the DownloadFileAsync method in LocalFileUploaderService to include a CancellationToken parameter, enabling cancellation of file download operations. The method now returns a Task to provide the downloaded file stream. --- Refhub/Service/Implement/LocalFileUploaderService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index edad5d6e..4c196a72 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -43,7 +43,7 @@ public Task DeleteFile(string realUrl) return Task.CompletedTask; } - public Task DownloadFileAsync(string fileName) + public Task DownloadFileAsync(string fileName, CancellationToken ct) { string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); @@ -55,4 +55,6 @@ public Task DownloadFileAsync(string fileName) Stream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); return Task.FromResult(fileStream); } + + } From 1eb193d2ce46fa6002fc92d6cf4727c68ff3f68b Mon Sep 17 00:00:00 2001 From: Hootan Hemmati Date: Tue, 1 Jul 2025 21:59:32 +0330 Subject: [PATCH 035/106] Fix CI (#103) * Remove final report generation step from CI workflow * Update ci.yml (#100) --- .github/workflows/ci.yml | 92 +--------------------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9f4bcd2..e7b7643c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,97 +225,7 @@ jobs: # ===================================== path: ./publish retention-days: 7 compression-level: 6 - # ===================================== - # 📊 FINAL STATUS REPORT - # ===================================== - status-report: - name: 📊 Build Status Report - runs-on: ubuntu-latest - needs: [build, docker, package] - if: always() - timeout-minutes: 5 - - steps: - - name: 📊 Generate Final Report - run: | - echo "🎉 BUILD PIPELINE REPORT" - echo "========================================" - echo "📅 Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "🏷️ Run: #${{ github.run_number }}" - echo "🔗 Repository: ${{ github.repository }}" - echo "🌿 Branch: ${{ github.ref_name }}" - echo "👤 Triggered by: ${{ github.actor }}" - echo "⚡ Event: ${{ github.event_name }}" - echo "" echo "📋 JOB STATUS SUMMARY:" - echo "========================================" - echo "🏗️ Build: ${{ needs.build.result }}" - echo "� Docker: ${{ needs.docker.result }}" - echo "�📦 Package: ${{ needs.package.result }}" - echo "" - - # Determine overall status - if [[ "${{ needs.build.result }}" == "success" && - "${{ needs.docker.result }}" == "success" && - "${{ needs.package.result }}" == "success" ]]; then - echo "🎉 OVERALL STATUS: ✅ SUCCESS" - echo "🚀 Build completed successfully!" - else - echo "❌ OVERALL STATUS: ⚠️ FAILED" - echo "🔧 Please review failed jobs above" - fi - echo "========================================" - - - name: 💬 PR Comment (On Failure) - if: failure() && github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `## ❌ Build Pipeline Failed - - **Build Status**: Failed ❌ - **Run ID**: ${{ github.run_id }} - **Commit**: ${{ github.sha }} - - Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. - - ### 🔧 Next Steps: - - Review the failed job logs - - Fix any compilation errors - - Ensure dependencies are properly restored - - --- - *This comment was automatically generated by the build pipeline* 🤖` - }) - - - name: 💬 PR Comment (On Success) - if: success() && github.event_name == 'pull_request' - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `## ✅ Build Pipeline Successful - - **Build Status**: Passed ✅ - **Run ID**: ${{ github.run_id }} - **Commit**: ${{ github.sha }} - ### 🎉 Build Completed: - - ✅ Build successful (Debug & Release) - - ✅ Docker image built and tested - - ✅ Artifacts created - - **Ready for review and merge!** 🚀 - - --- - *This comment was automatically generated by the build pipeline* 🤖` - }) - + # ===================================== # 🔧 WORKFLOW CONFIGURATION NOTES # ===================================== From bdda8a2bcd6d3e0be4a54dbffa1a742326225d87 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 11:07:15 +0330 Subject: [PATCH 036/106] update confilict Migration IX_Books_Slug on Add UniqueIndexToBookSlugAndModifyType --- .../20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs b/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs index 57859506..cc760785 100644 --- a/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs +++ b/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs @@ -17,7 +17,9 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, oldClrType: typeof(string), oldType: "nvarchar(max)"); - + migrationBuilder.DropIndex( + name: "IX_Books_Slug", + table: "Books"); migrationBuilder.CreateIndex( name: "IX_Books_Slug", table: "Books", From a34d966753681067d8ded46e3ea85de45bcb980c Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 11:07:30 +0330 Subject: [PATCH 037/106] add namespace ExtensionMethod --- Refhub/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Refhub/Program.cs b/Refhub/Program.cs index 1dbd25ba..74d91754 100644 --- a/Refhub/Program.cs +++ b/Refhub/Program.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Refhub.Data.Context; using Refhub.Tools.ExtensionMethod; +using Refhub.Tools.ExtentionMethod; namespace Refhub; From a85b80dc6ba1f76ce3f85b5679b62a3e0c5f6d9d Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 11:25:00 +0330 Subject: [PATCH 038/106] remove namespace ExtensionMethod --- Refhub/Program.cs | 2 +- .../ExtentionMethod/BindAppSettingToModelExtentionMethod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Refhub/Program.cs b/Refhub/Program.cs index 74d91754..3f7eeceb 100644 --- a/Refhub/Program.cs +++ b/Refhub/Program.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Refhub.Data.Context; using Refhub.Tools.ExtensionMethod; -using Refhub.Tools.ExtentionMethod; + namespace Refhub; diff --git a/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs index b3597e5f..2e1dcfdf 100644 --- a/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs +++ b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs @@ -2,7 +2,7 @@ using Refhub.Service.Implement; using Refhub.Service.Interface; -namespace Refhub.Tools.ExtentionMethod; +namespace Refhub.Tools.ExtensionMethod; public static class BindAppSettingToModelExtentionMethod { From 55cf60c96abf808f1a2b6c8b32b6be8cc2a791a4 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 14:59:15 +0330 Subject: [PATCH 039/106] move to secret.josn --- Refhub/appsettings.Development.json | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Refhub/appsettings.Development.json b/Refhub/appsettings.Development.json index 2d8a7cc0..1b3c284c 100644 --- a/Refhub/appsettings.Development.json +++ b/Refhub/appsettings.Development.json @@ -1,20 +1,9 @@ { - "AWS": { - "S3": { - "Region": "s3.ir-thr-at1.arvanstorage.ir", - "AccessKey": "", - "SecretKey": "", - "BucketName": "test12333555", - "ServiceURL": "https://s3.ir-thr-at1.arvanstorage.ir/" - } - }, + "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } - }, - "ConnectionStrings": { - "DefaultConnection": "Data Source=.;Initial Catalog=RefHubDB;Integrated Security=True;Multiple Active Result Sets=True;Trust Server Certificate=True" } } From 9028f8cfddf121f9a389478ee039580be92b61f3 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 14:59:48 +0330 Subject: [PATCH 040/106] remove unuse namespace --- Refhub/Controllers/BookController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 9abc4925..4cba3f34 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; -using Refhub.Service.Implement.S3_Sample.Service; + using Refhub.Service.Interface; namespace Refhub.Controllers; @@ -30,6 +30,10 @@ public async Task DownloadFile(string fileName, CancellationToken { try { + if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + { + return NotFound("خطا در دریافت فایل. لطفاً بعداً تلاش کنید."); + } // دریافت فایل از S3 var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct); From 6aff3cfb241df3241a2b339c22d71a4ffb2e690b Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:00:20 +0330 Subject: [PATCH 041/106] convert internal to public message resource --- Refhub/Resources/Messages.Designer.cs | 76 +++++++++++++-------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Refhub/Resources/Messages.Designer.cs b/Refhub/Resources/Messages.Designer.cs index a2641382..4fbce1ed 100644 --- a/Refhub/Resources/Messages.Designer.cs +++ b/Refhub/Resources/Messages.Designer.cs @@ -22,7 +22,7 @@ namespace Refhub.Resources { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { + public class Messages { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Messages() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Refhub.Resources.Messages", typeof(Messages).Assembly); @@ -51,7 +51,7 @@ internal Messages() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal Messages() { /// /// Looks up a localized string similar to The email entered is already registered.. /// - internal static string Account_EmailAlready { + public static string Account_EmailAlready { get { return ResourceManager.GetString("Account_EmailAlready", resourceCulture); } @@ -72,7 +72,7 @@ internal static string Account_EmailAlready { /// /// Looks up a localized string similar to An error has occurred.. /// - internal static string Account_Error_AddToRole { + public static string Account_Error_AddToRole { get { return ResourceManager.GetString("Account_Error_AddToRole", resourceCulture); } @@ -81,7 +81,7 @@ internal static string Account_Error_AddToRole { /// /// Looks up a localized string similar to The login attempt is invalid.. /// - internal static string Account_LoginInvalid { + public static string Account_LoginInvalid { get { return ResourceManager.GetString("Account_LoginInvalid", resourceCulture); } @@ -90,7 +90,7 @@ internal static string Account_LoginInvalid { /// /// Looks up a localized string similar to Registration encountered an error.. /// - internal static string Account_RegisterInvalid { + public static string Account_RegisterInvalid { get { return ResourceManager.GetString("Account_RegisterInvalid", resourceCulture); } @@ -99,7 +99,7 @@ internal static string Account_RegisterInvalid { /// /// Looks up a localized string similar to At least one associated author is required.. /// - internal static string Book_AnotherIdMinLength { + public static string Book_AnotherIdMinLength { get { return ResourceManager.GetString("Book_AnotherIdMinLength", resourceCulture); } @@ -108,7 +108,7 @@ internal static string Book_AnotherIdMinLength { /// /// Looks up a localized string similar to Choose a writer. /// - internal static string Book_AnotherIdRequired { + public static string Book_AnotherIdRequired { get { return ResourceManager.GetString("Book_AnotherIdRequired", resourceCulture); } @@ -117,7 +117,7 @@ internal static string Book_AnotherIdRequired { /// /// Looks up a localized string similar to Choose a category. /// - internal static string Book_CategoryIdRequired { + public static string Book_CategoryIdRequired { get { return ResourceManager.GetString("Book_CategoryIdRequired", resourceCulture); } @@ -126,7 +126,7 @@ internal static string Book_CategoryIdRequired { /// /// Looks up a localized string similar to Select a file. /// - internal static string Book_FileRequired { + public static string Book_FileRequired { get { return ResourceManager.GetString("Book_FileRequired", resourceCulture); } @@ -135,7 +135,7 @@ internal static string Book_FileRequired { /// /// Looks up a localized string similar to Select a Image. /// - internal static string Book_ImageRequired { + public static string Book_ImageRequired { get { return ResourceManager.GetString("Book_ImageRequired", resourceCulture); } @@ -144,7 +144,7 @@ internal static string Book_ImageRequired { /// /// Looks up a localized string similar to Enter the number of pages and select. /// - internal static string Book_PageCountRequired { + public static string Book_PageCountRequired { get { return ResourceManager.GetString("Book_PageCountRequired", resourceCulture); } @@ -153,7 +153,7 @@ internal static string Book_PageCountRequired { /// /// Looks up a localized string similar to The title length in the browser should be a maximum of 450 characters.. /// - internal static string Book_SlugMaxLength { + public static string Book_SlugMaxLength { get { return ResourceManager.GetString("Book_SlugMaxLength", resourceCulture); } @@ -162,7 +162,7 @@ internal static string Book_SlugMaxLength { /// /// Looks up a localized string similar to Enter the title in the browser.. /// - internal static string Book_SlugRequired { + public static string Book_SlugRequired { get { return ResourceManager.GetString("Book_SlugRequired", resourceCulture); } @@ -171,7 +171,7 @@ internal static string Book_SlugRequired { /// /// Looks up a localized string similar to Enter the book title. /// - internal static string Book_TitleRequired { + public static string Book_TitleRequired { get { return ResourceManager.GetString("Book_TitleRequired", resourceCulture); } @@ -180,7 +180,7 @@ internal static string Book_TitleRequired { /// /// Looks up a localized string similar to Enter the description of category book . /// - internal static string Category_Description { + public static string Category_Description { get { return ResourceManager.GetString("Category_Description", resourceCulture); } @@ -189,7 +189,7 @@ internal static string Category_Description { /// /// Looks up a localized string similar to Enter the category book title. /// - internal static string Category_NameRequired { + public static string Category_NameRequired { get { return ResourceManager.GetString("Category_NameRequired", resourceCulture); } @@ -198,7 +198,7 @@ internal static string Category_NameRequired { /// /// Looks up a localized string similar to Confirm Password Invalid. /// - internal static string ConfirmPassword_Compare_Invalid { + public static string ConfirmPassword_Compare_Invalid { get { return ResourceManager.GetString("ConfirmPassword_Compare_Invalid", resourceCulture); } @@ -207,7 +207,7 @@ internal static string ConfirmPassword_Compare_Invalid { /// /// Looks up a localized string similar to Enter Confirm Password!. /// - internal static string ConfirmPassword_Required { + public static string ConfirmPassword_Required { get { return ResourceManager.GetString("ConfirmPassword_Required", resourceCulture); } @@ -216,7 +216,7 @@ internal static string ConfirmPassword_Required { /// /// Looks up a localized string similar to The Email is not Valid!. /// - internal static string Email_Format_Invalid { + public static string Email_Format_Invalid { get { return ResourceManager.GetString("Email_Format_Invalid", resourceCulture); } @@ -225,7 +225,7 @@ internal static string Email_Format_Invalid { /// /// Looks up a localized string similar to Enter the Email . /// - internal static string Email_Required { + public static string Email_Required { get { return ResourceManager.GetString("Email_Required", resourceCulture); } @@ -234,7 +234,7 @@ internal static string Email_Required { /// /// Looks up a localized string similar to Author not found. /// - internal static string Error_AuthorNotFound { + public static string Error_AuthorNotFound { get { return ResourceManager.GetString("Error_AuthorNotFound", resourceCulture); } @@ -243,7 +243,7 @@ internal static string Error_AuthorNotFound { /// /// Looks up a localized string similar to Uncertain. /// - internal static string Error_NotDefined { + public static string Error_NotDefined { get { return ResourceManager.GetString("Error_NotDefined", resourceCulture); } @@ -252,7 +252,7 @@ internal static string Error_NotDefined { /// /// Looks up a localized string similar to Slug has already been used.. /// - internal static string Error_SlugExists { + public static string Error_SlugExists { get { return ResourceManager.GetString("Error_SlugExists", resourceCulture); } @@ -261,7 +261,7 @@ internal static string Error_SlugExists { /// /// Looks up a localized string similar to FullName is Required. /// - internal static string FullNameRequired { + public static string FullNameRequired { get { return ResourceManager.GetString("FullNameRequired", resourceCulture); } @@ -270,7 +270,7 @@ internal static string FullNameRequired { /// /// Looks up a localized string similar to Keyword found. /// - internal static string Keyword_AlreadyExists { + public static string Keyword_AlreadyExists { get { return ResourceManager.GetString("Keyword_AlreadyExists", resourceCulture); } @@ -279,7 +279,7 @@ internal static string Keyword_AlreadyExists { /// /// Looks up a localized string similar to Keyword not found. /// - internal static string Keyword_NotFound { + public static string Keyword_NotFound { get { return ResourceManager.GetString("Keyword_NotFound", resourceCulture); } @@ -288,7 +288,7 @@ internal static string Keyword_NotFound { /// /// Looks up a localized string similar to choose a keyword!. /// - internal static string Keyword_Required { + public static string Keyword_Required { get { return ResourceManager.GetString("Keyword_Required", resourceCulture); } @@ -297,7 +297,7 @@ internal static string Keyword_Required { /// /// Looks up a localized string similar to The name must be a maximum of 50 characters.. /// - internal static string Name_Max_50 { + public static string Name_Max_50 { get { return ResourceManager.GetString("Name_Max_50", resourceCulture); } @@ -306,7 +306,7 @@ internal static string Name_Max_50 { /// /// Looks up a localized string similar to Must contain at least one letter, one number and 6 characters.. /// - internal static string Password_Regex_Invalid { + public static string Password_Regex_Invalid { get { return ResourceManager.GetString("Password_Regex_Invalid", resourceCulture); } @@ -315,7 +315,7 @@ internal static string Password_Regex_Invalid { /// /// Looks up a localized string similar to Enter The Password!. /// - internal static string Password_Required { + public static string Password_Required { get { return ResourceManager.GetString("Password_Required", resourceCulture); } @@ -324,7 +324,7 @@ internal static string Password_Required { /// /// Looks up a localized string similar to Remember Me! . /// - internal static string RememberMe_Display { + public static string RememberMe_Display { get { return ResourceManager.GetString("RememberMe_Display", resourceCulture); } @@ -333,7 +333,7 @@ internal static string RememberMe_Display { /// /// Looks up a localized string similar to The slug must be a maximum of 450 characters.. /// - internal static string Slug_Max_450 { + public static string Slug_Max_450 { get { return ResourceManager.GetString("Slug_Max_450", resourceCulture); } @@ -342,7 +342,7 @@ internal static string Slug_Max_450 { /// /// Looks up a localized string similar to Choose a slug name. /// - internal static string Slug_NameRequired { + public static string Slug_NameRequired { get { return ResourceManager.GetString("Slug_NameRequired", resourceCulture); } @@ -351,7 +351,7 @@ internal static string Slug_NameRequired { /// /// Looks up a localized string similar to Slug is Required. /// - internal static string SlugRequired { + public static string SlugRequired { get { return ResourceManager.GetString("SlugRequired", resourceCulture); } @@ -360,7 +360,7 @@ internal static string SlugRequired { /// /// Looks up a localized string similar to refhub. /// - internal static string Title { + public static string Title { get { return ResourceManager.GetString("Title", resourceCulture); } @@ -369,7 +369,7 @@ internal static string Title { /// /// Looks up a localized string similar to User is not found. /// - internal static string User_NotFound { + public static string User_NotFound { get { return ResourceManager.GetString("User_NotFound", resourceCulture); } From cd25eae388e67baa2824e32d7bdea985289be835 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:00:47 +0330 Subject: [PATCH 042/106] add SaveChangesAsync on CreateAnotherAsync --- Refhub/Service/Implement/BookService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index c5ab2785..d4f7f73c 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -100,6 +100,7 @@ public async Task CreateAnotherAsync(string fullname, string slug, Cancell var author = new Author { FullName = fullname, Slug = slug }; await context.Authors.AddAsync(author, ct); + await context.SaveChangesAsync(ct); return true; } @@ -149,6 +150,8 @@ public async Task CreateBookAsync(CreateBookVM book, CancellationToken ct) { try { + if (context.Books.Any(a => a.Slug.Equals(book.Slug))) + return false; var bookAuthors = book.AnotherId.Select(a => new BookAuthor { AuthorId = a From 109691bef62aa4dba2fbf47cb7678b4416a369a9 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:01:11 +0330 Subject: [PATCH 043/106] merg namespace --- Refhub/Service/Implement/S3FileUploaderService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index b56bd270..4ba00a7c 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -4,8 +4,7 @@ namespace Refhub.Service.Implement { - namespace S3_Sample.Service - { + using Amazon; using Amazon.Runtime; using Amazon.S3; @@ -111,6 +110,6 @@ public async Task DownloadFileAsync(string fileName, CancellationToken c } - } + } From af15a976bba47ff5617dd795305dbad2d9a880ec Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:01:53 +0330 Subject: [PATCH 044/106] PublicResXFileCodeGenerator --- Refhub/Refhub.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index 9cf7db4f..7596a291 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -27,7 +27,7 @@ - ResXFileCodeGenerator + PublicResXFileCodeGenerator Messages.Designer.cs From 1540595696038fdf5c6c3fbe38ae3b76a946c943 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:05:11 +0330 Subject: [PATCH 045/106] remove unuse namepace --- Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs b/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs index db7544a2..13f1781e 100644 --- a/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs +++ b/Refhub/Tools/ExtentionMethod/AddServiceExtentionMethod.cs @@ -1,5 +1,5 @@ using Refhub.Service.Implement; -using Refhub.Service.Implement.S3_Sample.Service; + using Refhub.Service.Interface; namespace Refhub.Tools.ExtensionMethod; From 3aa2a5fa36c170ed1838657d95687a3112a2bbca Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sat, 12 Jul 2025 15:07:30 +0330 Subject: [PATCH 046/106] fix PublicResXFileCodeGeneratorTargets --- Refhub/Refhub.csproj | 49 +++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index 7596a291..e77b7974 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -1,30 +1,33 @@ - + - - net9.0 - enable - enable - c6b529e8-deca-497c-84d4-0bddb8042e65 - + + net9.0 + enable + enable + c6b529e8-deca-497c-84d4-0bddb8042e65 + - - - - + + + + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + true + PublicResXFileCodeGenerator From 166a0262c479640160559288d3d5c062f2593a54 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:04:15 +0330 Subject: [PATCH 047/106] add BucketNameStatic.GetName --- Refhub/Controllers/BookController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 4cba3f34..372c80fd 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -2,8 +2,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; - +using Refhub.Models.Enums; using Refhub.Service.Interface; +using Refhub.Tools.Static; namespace Refhub.Controllers; @@ -35,7 +36,7 @@ public async Task DownloadFile(string fileName, CancellationToken return NotFound("خطا در دریافت فایل. لطفاً بعداً تلاش کنید."); } // دریافت فایل از S3 - var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct); + var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct, BucketNameStatic.GetName(BucketNames.BookPdf)); // تعیین نوع فایل با توجه به پسوند var contentTypeProvider = new FileExtensionContentTypeProvider(); From d698f083757f88674be442309dd7c2f264d00005 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:04:29 +0330 Subject: [PATCH 048/106] add enum BucketNames --- Refhub/Models/Enums/BucketNames.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Refhub/Models/Enums/BucketNames.cs diff --git a/Refhub/Models/Enums/BucketNames.cs b/Refhub/Models/Enums/BucketNames.cs new file mode 100644 index 00000000..c6bb8e91 --- /dev/null +++ b/Refhub/Models/Enums/BucketNames.cs @@ -0,0 +1,12 @@ +namespace Refhub.Models.Enums +{ + public enum BucketNames + { + None = 0, + BookImages, + BookPdf, + + + } + +} From cdd3c794c0487e00eb284b4e55ae7c4d0027fddc Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:04:49 +0330 Subject: [PATCH 049/106] update namespace --- Refhub/Models/Enums/DirectoryTypes.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Refhub/Models/Enums/DirectoryTypes.cs b/Refhub/Models/Enums/DirectoryTypes.cs index 9d6a5d1b..943b81f5 100644 --- a/Refhub/Models/Enums/DirectoryTypes.cs +++ b/Refhub/Models/Enums/DirectoryTypes.cs @@ -9,5 +9,4 @@ public enum DirectoryTypes } - } From 9425cf7062aa5f379d11b0fe97b1d7acf79788e3 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:05:01 +0330 Subject: [PATCH 050/106] update IFileUploaderService --- Refhub/Service/Interface/IFileUploaderService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index b6cbd7a7..a9749e64 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -2,8 +2,7 @@ public interface IFileUploaderService { - Task UploadFile(IFormFile file, string directoryName, string Type, string Name); - - Task DeleteFile(string realUrl); - Task DownloadFileAsync(string fileName,CancellationToken ct); + Task UploadFile(IFormFile file, string directoryName, string name); + Task DeleteFile(string realUrl, string bucketName); + Task DownloadFileAsync(string fileName,CancellationToken ct, string? bucketName); } From 7e7822e9a53887cb64ed4f448790ac4389af944b Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:05:14 +0330 Subject: [PATCH 051/106] add BucketNameStatic --- Refhub/Tools/Static/BucketNameStatic.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Refhub/Tools/Static/BucketNameStatic.cs diff --git a/Refhub/Tools/Static/BucketNameStatic.cs b/Refhub/Tools/Static/BucketNameStatic.cs new file mode 100644 index 00000000..8dba74b3 --- /dev/null +++ b/Refhub/Tools/Static/BucketNameStatic.cs @@ -0,0 +1,18 @@ +using Refhub.Models.Enums; + +namespace Refhub.Tools.Static; + +public static class BucketNameStatic +{ + public static string GetName(BucketNames bucketNames) + { + return bucketNames switch + { + BucketNames.BookPdf => nameof(BucketNames.BookPdf), + BucketNames.BookImages => nameof(BucketNames.BookImages), + + + _ => throw new ArgumentOutOfRangeException(nameof(bucketNames), $"Unsupported directory type: {bucketNames}") + }; + } +} From 1745ceb282313c8996f17094e8facf921b5c894e Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:05:37 +0330 Subject: [PATCH 052/106] use BucketNameStatic.GetName on bookService --- Refhub/Service/Implement/BookService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index d4f7f73c..c627e6f6 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -157,8 +157,8 @@ public async Task CreateBookAsync(CreateBookVM book, CancellationToken ct) AuthorId = a }).ToList(); - var filePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); - var imagePath = await uploaderService.UploadFile(book.Image, FolderNameStatic.GetDirectoryName(DirectoryTypes.Images), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); + var filePath = await uploaderService.UploadFile(book.File, BucketNameStatic.GetName(BucketNames.BookPdf), book.Slug); + var imagePath = await uploaderService.UploadFile(book.Image, BucketNameStatic.GetName(BucketNames.BookImages), book.Slug); if (string.IsNullOrWhiteSpace(filePath) || string.IsNullOrWhiteSpace(imagePath)) { @@ -212,20 +212,20 @@ public async Task UpdateBookAsync(UpdateBookVM book, CancellationToken ct) { if (!string.IsNullOrWhiteSpace(existingBook.FilePath)) { - await uploaderService.DeleteFile(existingBook.FilePath); + await uploaderService.DeleteFile(existingBook.FilePath, BucketNameStatic.GetName(BucketNames.BookPdf)); } - existingBook.FilePath = await uploaderService.UploadFile(book.File, FolderNameStatic.GetDirectoryName(DirectoryTypes.Files), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); + existingBook.FilePath = await uploaderService.UploadFile(book.File, BucketNameStatic.GetName(BucketNames.BookPdf), book.Slug); } if (book.Image != null) { if (!string.IsNullOrWhiteSpace(existingBook.ImagePath)) { - await uploaderService.DeleteFile(existingBook.ImagePath); + await uploaderService.DeleteFile(existingBook.ImagePath, BucketNameStatic.GetName(BucketNames.BookImages)); } - existingBook.ImagePath = await uploaderService.UploadFile(book.Image, FolderNameStatic.GetDirectoryName(DirectoryTypes.Images), FolderNameStatic.GetDirectoryName(DirectoryTypes.Books), book.Slug); + existingBook.ImagePath = await uploaderService.UploadFile(book.Image, BucketNameStatic.GetName(BucketNames.BookImages), book.Slug); } // حذف نویسنده‌های قبلی و اضافه کردن جدید @@ -266,12 +266,12 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) if (!string.IsNullOrWhiteSpace(book.ImagePath)) { - await uploaderService.DeleteFile(book.ImagePath); + await uploaderService.DeleteFile(book.ImagePath, BucketNameStatic.GetName(BucketNames.BookImages)); } if (!string.IsNullOrWhiteSpace(book.FilePath)) { - await uploaderService.DeleteFile(book.FilePath); + await uploaderService.DeleteFile(book.FilePath, BucketNameStatic.GetName(BucketNames.BookPdf)); } var relations = await context.BookRelations From 1e7ca351907df253a1a180d6f67eefcecbf166e6 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Sun, 13 Jul 2025 00:06:17 +0330 Subject: [PATCH 053/106] use bucketName by paramtr --- .../Implement/LocalFileUploaderService.cs | 8 +- .../Implement/S3FileUploaderService.cs | 171 +++++++++--------- 2 files changed, 90 insertions(+), 89 deletions(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index 4c196a72..88f6515d 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -6,10 +6,10 @@ public class LocalFileUploaderService : IFileUploaderService { private readonly string _rootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files"); - public async Task UploadFile(IFormFile file, string directoryName, string type, string name) + public async Task UploadFile(IFormFile file, string directoryName, string name) { // ایجاد مسیر دایرکتوری نهایی - string targetDirectory = Path.Combine(_rootPath, type, directoryName); + string targetDirectory = Path.Combine(_rootPath, directoryName); // ایجاد پوشه‌ها در صورت نبود if (!Directory.Exists(targetDirectory)) @@ -31,7 +31,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin return fileName; } - public Task DeleteFile(string realUrl) + public Task DeleteFile(string realUrl, string bucketName) { string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", realUrl.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); @@ -43,7 +43,7 @@ public Task DeleteFile(string realUrl) return Task.CompletedTask; } - public Task DownloadFileAsync(string fileName, CancellationToken ct) + public Task DownloadFileAsync(string fileName, CancellationToken ct, string? bucketName) { string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 4ba00a7c..0d51a0bb 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -4,112 +4,113 @@ namespace Refhub.Service.Implement { - - using Amazon; - using Amazon.Runtime; - using Amazon.S3; - using Amazon.S3.Model; - using System.Threading; - - public class S3FileUploaderService : IFileUploaderService + + using Amazon; + using Amazon.Runtime; + using Amazon.S3; + using Amazon.S3.Model; + using Refhub.Models.Enums; + using System.Threading; + + public class S3FileUploaderService : IFileUploaderService + { + private readonly string _bucketName; + private readonly string _region; + private readonly IAmazonS3 _s3Client; + private readonly IOptions _s3Options; + + public S3FileUploaderService(IOptions s3Options) { - private readonly string _bucketName; - private readonly string _region; - private readonly IAmazonS3 _s3Client; - private readonly IOptions _s3Options; + this._s3Options = s3Options; + + var credentials = new BasicAWSCredentials(s3Options.Value.AccessKey, s3Options.Value.SecretKey); - public S3FileUploaderService(IOptions s3Options) + var config = new AmazonS3Config { - this._s3Options = s3Options; + RegionEndpoint = RegionEndpoint.USEast1, // منطقه ساختگی، چون ArvanRegion اختصاصی داره + ServiceURL = s3Options.Value.ServiceURL, + ForcePathStyle = true // بسیار مهم برای کار با آروان‌کلاد + }; - var credentials = new BasicAWSCredentials(s3Options.Value.AccessKey, s3Options.Value.SecretKey); + _s3Client = new AmazonS3Client(credentials, config); - var config = new AmazonS3Config - { - RegionEndpoint = RegionEndpoint.USEast1, // منطقه ساختگی، چون ArvanRegion اختصاصی داره - ServiceURL = s3Options.Value.ServiceURL, - ForcePathStyle = true // بسیار مهم برای کار با آروان‌کلاد - }; + _region = s3Options.Value.Region; + //_bucketName = s3Options.Value.BucketName; + } + private string GenerateS3Url(string key, string bucketName) + { + // برای آروان کلاد: + return $"{_s3Options.Value.ServiceURL}/{bucketName.ToLower()}/{key}"; + } - _s3Client = new AmazonS3Client(credentials, config); - _region = s3Options.Value.Region; - _bucketName = s3Options.Value.BucketName; - } - private string GenerateS3Url(string key) - { - // برای آروان کلاد: - return $"{_s3Options.Value.ServiceURL}/{_bucketName}/{key}"; - } + private string GetKey(string realUrl ,string bucketName) + { + // برای آروان کلاد: + return realUrl.Replace($"{_s3Options.Value.ServiceURL}/{bucketName.ToLower()}/", ""); - private string GetKey(string realUrl) - { - // برای آروان کلاد: + } - return realUrl.Replace($"{_s3Options.Value.ServiceURL}/{_bucketName}/", ""); + public async Task UploadFile(IFormFile file, string directoryName, string name) + { + var bucketName = directoryName; + var key = $"{name.Replace(" ", "-")}{Path.GetExtension(file.FileName)}"; - } - public async Task UploadFile(IFormFile file, string directoryName, string type, string name) + using var stream = file.OpenReadStream(); + var request = new PutObjectRequest { - var bucketName = _bucketName; - var key = $"{directoryName}/{type}/{name.Replace(" ", "-")}{Path.GetExtension(file.FileName)}"; - - using var stream = file.OpenReadStream(); - var request = new PutObjectRequest - { - BucketName = bucketName, - Key = key, - InputStream = stream, - ContentType = file.ContentType, - - CannedACL = S3CannedACL.PublicRead - }; - - await _s3Client.PutObjectAsync(request); - return GenerateS3Url(key); - } + BucketName = bucketName.ToLower(), + Key = key, + InputStream = stream, + ContentType = file.ContentType, + CannedACL = S3CannedACL.PublicRead + }; - public async Task DeleteFile(string realUrl) + await _s3Client.PutObjectAsync(request); + return GenerateS3Url(key, bucketName); + } + + public async Task DeleteFile(string realUrl,string? bucketName) + { + + var key = GetKey(realUrl, bucketName); + + var request = new DeleteObjectRequest { + BucketName = bucketName.ToLower(), + Key = key + }; - var key = GetKey(realUrl); + await _s3Client.DeleteObjectAsync(request); + } - var request = new DeleteObjectRequest - { - BucketName = _bucketName, - Key = key - }; + public async Task DownloadFileAsync(string fileName, CancellationToken ct, string? bucketName) + { + var request = new GetObjectRequest + { + BucketName = bucketName.ToLower(), + Key = fileName // مثلاً: "images/profile.jpg" + }; - await _s3Client.DeleteObjectAsync(request); + try + { + using var response = await _s3Client.GetObjectAsync(request, ct); + var memoryStream = new MemoryStream(); + await response.ResponseStream.CopyToAsync(memoryStream, ct); + memoryStream.Position = 0; // مهم! برای اینکه موقع Return از ابتدا خونده بشه + return memoryStream; } - - public async Task DownloadFileAsync(string fileName, CancellationToken ct) + catch (AmazonS3Exception ex) { - var request = new GetObjectRequest - { - BucketName = _bucketName, - Key = fileName // مثلاً: "images/profile.jpg" - }; - - try - { - using var response = await _s3Client.GetObjectAsync(request,ct); - var memoryStream = new MemoryStream(); - await response.ResponseStream.CopyToAsync(memoryStream,ct); - memoryStream.Position = 0; // مهم! برای اینکه موقع Return از ابتدا خونده بشه - return memoryStream; - } - catch (AmazonS3Exception ex) - { - // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود - throw new Exception($"خطا در دانلود فایل از S3: {ex.Message}", ex); - } + // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود + throw new Exception($"خطا در دانلود فایل از S3: {ex.Message}", ex); } - - } - + + + } + } From 2d34fb932c166cecfee505c6e2a108b6c61601a6 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:27:19 +0330 Subject: [PATCH 054/106] add ILogger --- Refhub/Controllers/BookController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 372c80fd..0b84d2a6 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -8,7 +8,7 @@ namespace Refhub.Controllers; -public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService,ILogger _logger) : Controller +public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService, ILogger _logger) : Controller { [HttpGet("BookDetails/{slug}")] public async Task BookDetails(string slug, CancellationToken ct) From e245c012fa377d2d4b5fc853876c853d5b3143fc Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:27:43 +0330 Subject: [PATCH 055/106] add AnyAsync book Service --- Refhub/Service/Implement/BookService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index c627e6f6..82efc17d 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -150,7 +150,7 @@ public async Task CreateBookAsync(CreateBookVM book, CancellationToken ct) { try { - if (context.Books.Any(a => a.Slug.Equals(book.Slug))) + if (await context.Books.AnyAsync(a => a.Slug.Equals(book.Slug), ct)) return false; var bookAuthors = book.AnotherId.Select(a => new BookAuthor { From d5f3a3e07e50c080209a05319ea6e3dbec7e016f Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:27:58 +0330 Subject: [PATCH 056/106] update RegionEndpoint for s3 --- Refhub/Service/Implement/S3FileUploaderService.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 0d51a0bb..4e14bb64 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -14,8 +14,6 @@ namespace Refhub.Service.Implement public class S3FileUploaderService : IFileUploaderService { - private readonly string _bucketName; - private readonly string _region; private readonly IAmazonS3 _s3Client; private readonly IOptions _s3Options; @@ -27,14 +25,13 @@ public S3FileUploaderService(IOptions s3Options) var config = new AmazonS3Config { - RegionEndpoint = RegionEndpoint.USEast1, // منطقه ساختگی، چون ArvanRegion اختصاصی داره + RegionEndpoint = RegionEndpoint.GetBySystemName(s3Options.Value.Region), // Dynamically set region endpoint ServiceURL = s3Options.Value.ServiceURL, ForcePathStyle = true // بسیار مهم برای کار با آروان‌کلاد }; _s3Client = new AmazonS3Client(credentials, config); - _region = s3Options.Value.Region; //_bucketName = s3Options.Value.BucketName; } private string GenerateS3Url(string key, string bucketName) From 1331ac2edf64106fa682fa7b6117c8f56b430f4a Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:28:11 +0330 Subject: [PATCH 057/106] change name BindAppSettingToModelExtensionMethod --- .../ExtentionMethod/BindAppSettingToModelExtentionMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs index 2e1dcfdf..f92440b6 100644 --- a/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs +++ b/Refhub/Tools/ExtentionMethod/BindAppSettingToModelExtentionMethod.cs @@ -4,7 +4,7 @@ namespace Refhub.Tools.ExtensionMethod; -public static class BindAppSettingToModelExtentionMethod +public static class BindAppSettingToModelExtensionMethod { public static WebApplicationBuilder BindS3Model(this WebApplicationBuilder builder) { From 9e79c585774b0f7ff4b0c87ab8d7865c74aa0995 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:58:53 +0330 Subject: [PATCH 058/106] remove pathExtensionMethod --- Refhub/Areas/Admin/Views/ManageBook/Index.cshtml | 2 +- Refhub/Areas/Admin/Views/ManageBook/Update.cshtml | 2 +- Refhub/Components/Views/Home/BestBookView.cshtml | 2 +- Refhub/Components/Views/Home/LastBookView.cshtml | 2 +- Refhub/Tools/ExtentionMethod/PathExtionMethod.cs | 15 --------------- Refhub/Views/Book/BookDetails.cshtml | 4 ++-- Refhub/Views/Book/List.cshtml | 2 +- 7 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 Refhub/Tools/ExtentionMethod/PathExtionMethod.cs diff --git a/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml index 4f38e1e7..61f21505 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml @@ -47,7 +47,7 @@ @foreach (var item in Model) { - + @item.Title حذف diff --git a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml index 4811439f..ec235919 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml @@ -47,7 +47,7 @@
- + diff --git a/Refhub/Components/Views/Home/BestBookView.cshtml b/Refhub/Components/Views/Home/BestBookView.cshtml index c4dbfdbc..d7d348e1 100644 --- a/Refhub/Components/Views/Home/BestBookView.cshtml +++ b/Refhub/Components/Views/Home/BestBookView.cshtml @@ -21,7 +21,7 @@
- @item.Title + @item.Title
diff --git a/Refhub/Components/Views/Home/LastBookView.cshtml b/Refhub/Components/Views/Home/LastBookView.cshtml index c163caa7..8cfaa488 100644 --- a/Refhub/Components/Views/Home/LastBookView.cshtml +++ b/Refhub/Components/Views/Home/LastBookView.cshtml @@ -25,7 +25,7 @@
- @item.Title + @item.Title
diff --git a/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs b/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs deleted file mode 100644 index 9bbc3721..00000000 --- a/Refhub/Tools/ExtentionMethod/PathExtionMethod.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Refhub.Tools.Static; - -namespace Refhub.Tools.ExtensionMethod; - -public static class PathImageExtionMethod -{ - public static string ConvertForBookPathImage(this string imageName) - { - return imageName; - } - public static string ConvertForBookPathFile(this string fileName) - { - return fileName; - } -} diff --git a/Refhub/Views/Book/BookDetails.cshtml b/Refhub/Views/Book/BookDetails.cshtml index fb4d594b..1831bbdf 100644 --- a/Refhub/Views/Book/BookDetails.cshtml +++ b/Refhub/Views/Book/BookDetails.cshtml @@ -22,7 +22,7 @@ @if (!string.IsNullOrEmpty(Model.ImagePath)) {
- @(Model.Title ?? + @(Model.Title ??
} else @@ -74,7 +74,7 @@ @if (!string.IsNullOrEmpty(Model.FilePath)) { } else diff --git a/Refhub/Views/Book/List.cshtml b/Refhub/Views/Book/List.cshtml index ac609b73..8645791e 100644 --- a/Refhub/Views/Book/List.cshtml +++ b/Refhub/Views/Book/List.cshtml @@ -100,7 +100,7 @@ Page content START --> From dd31d93c4bce0c4379af59d11a49cdba0b5398d5 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:59:07 +0330 Subject: [PATCH 059/106] use _messageService --- Refhub/Controllers/BookController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 0b84d2a6..92150300 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -3,12 +3,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Refhub.Models.Enums; +using Refhub.Service.Implement; using Refhub.Service.Interface; using Refhub.Tools.Static; namespace Refhub.Controllers; -public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService, ILogger _logger) : Controller +public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService, ILogger _logger, IMessageService _messageService) : Controller { [HttpGet("BookDetails/{slug}")] public async Task BookDetails(string slug, CancellationToken ct) @@ -33,7 +34,7 @@ public async Task DownloadFile(string fileName, CancellationToken { if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { - return NotFound("خطا در دریافت فایل. لطفاً بعداً تلاش کنید."); + return NotFound(_messageService.Get("InvalidFileName")); } // دریافت فایل از S3 var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct, BucketNameStatic.GetName(BucketNames.BookPdf)); @@ -50,12 +51,12 @@ public async Task DownloadFile(string fileName, CancellationToken catch (AmazonS3Exception s3Ex) { _logger.LogError(s3Ex, "خطا در دانلود فایل از S3: {Message}", s3Ex.Message); - return NotFound("خطا در دریافت فایل. لطفاً بعداً تلاش کنید."); + return NotFound(_messageService.Get("FileNotFound")); } catch (Exception ex) { _logger.LogError(ex, "خطای پیش‌بینی‌نشده هنگام دانلود فایل."); - return StatusCode(500, "خطای غیرمنتظره‌ای رخ داده است. لطفاً با پشتیبانی تماس بگیرید."); + return StatusCode(500, _messageService.Get("DownloadError")); } } From 1448a4c646565ab28fab07aa0d698e3e11c08786 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:59:20 +0330 Subject: [PATCH 060/106] remove comment code --- Refhub/Data/Configuration/BookAuthorConfiguration.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Refhub/Data/Configuration/BookAuthorConfiguration.cs b/Refhub/Data/Configuration/BookAuthorConfiguration.cs index d7214dea..fd59d582 100644 --- a/Refhub/Data/Configuration/BookAuthorConfiguration.cs +++ b/Refhub/Data/Configuration/BookAuthorConfiguration.cs @@ -11,10 +11,6 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(ba => new { ba.BookId, ba.AuthorId }); - //builder.HasOne(ba => ba.Book) - //.WithMany(b => b.BookAuthors) - //.HasForeignKey(ba => ba.BookId) - //.OnDelete(DeleteBehavior.Cascade); builder.HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) From 45838b79b764b897a466dfbc62b6a270c9ef4ccb Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 09:59:44 +0330 Subject: [PATCH 061/106] refactor GetKey code on S3 --- .../Implement/S3FileUploaderService.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 4e14bb64..9bda2867 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -10,6 +10,7 @@ namespace Refhub.Service.Implement using Amazon.S3; using Amazon.S3.Model; using Refhub.Models.Enums; + using Refhub.Tools.Exceptions; using System.Threading; public class S3FileUploaderService : IFileUploaderService @@ -41,15 +42,23 @@ private string GenerateS3Url(string key, string bucketName) } - private string GetKey(string realUrl ,string bucketName) + private string GetKey(string realUrl, string bucketName) { // برای آروان کلاد: - return realUrl.Replace($"{_s3Options.Value.ServiceURL}/{bucketName.ToLower()}/", ""); + var prefix = $"{_s3Options.Value.ServiceURL}/{bucketName.ToLower()}/"; + + if (realUrl.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + return realUrl.Substring(prefix.Length); + } + + throw new ArgumentException("آدرس مورد نظر با پیشوند تعیین‌شده شروع نمی‌شود."); + } - public async Task UploadFile(IFormFile file, string directoryName, string name) + public async Task UploadFile(IFormFile file, string directoryName, string name) { var bucketName = directoryName; var key = $"{name.Replace(" ", "-")}{Path.GetExtension(file.FileName)}"; @@ -69,7 +78,7 @@ public async Task UploadFile(IFormFile file, string directoryName, stri return GenerateS3Url(key, bucketName); } - public async Task DeleteFile(string realUrl,string? bucketName) + public async Task DeleteFile(string realUrl, string? bucketName) { var key = GetKey(realUrl, bucketName); @@ -102,7 +111,7 @@ public async Task DownloadFileAsync(string fileName, CancellationToken c catch (AmazonS3Exception ex) { // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود - throw new Exception($"خطا در دانلود فایل از S3: {ex.Message}", ex); + throw new FileDownloadException("خطا در دانلود فایل از S3", ex); } } From b0a3195c0c61bc9fc63f63d354ffbc62c70aa3d8 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:00:22 +0330 Subject: [PATCH 062/106] add FileDownloadException --- Refhub/Tools/Exceptions/EntityNotFoundException.cs | 1 - Refhub/Tools/Exceptions/FileDownloadException.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 Refhub/Tools/Exceptions/FileDownloadException.cs diff --git a/Refhub/Tools/Exceptions/EntityNotFoundException.cs b/Refhub/Tools/Exceptions/EntityNotFoundException.cs index add63e75..4c4a6e48 100644 --- a/Refhub/Tools/Exceptions/EntityNotFoundException.cs +++ b/Refhub/Tools/Exceptions/EntityNotFoundException.cs @@ -10,5 +10,4 @@ public EntityNotFoundException(string message, Exception innerException) : base( } } - } diff --git a/Refhub/Tools/Exceptions/FileDownloadException.cs b/Refhub/Tools/Exceptions/FileDownloadException.cs new file mode 100644 index 00000000..999716d0 --- /dev/null +++ b/Refhub/Tools/Exceptions/FileDownloadException.cs @@ -0,0 +1,13 @@ +namespace Refhub.Tools.Exceptions +{ + public class FileDownloadException : Exception + { + public FileDownloadException (string message) : base(message) + { + } + public FileDownloadException(string message, Exception innerException) : base(message, innerException) + { + } + + } +} From 1546e77a18d524e6a513d9e54e9ea38700fb8316 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:00:45 +0330 Subject: [PATCH 063/106] add message DownloadError,InvalidFileName,FileNotFound --- Refhub/Resources/Messages.Designer.cs | 27 +++++++++++++++++++++++++++ Refhub/Resources/Messages.fa.resx | 13 +++++++++++-- Refhub/Resources/Messages.resx | 9 +++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Refhub/Resources/Messages.Designer.cs b/Refhub/Resources/Messages.Designer.cs index 4fbce1ed..13efc7d4 100644 --- a/Refhub/Resources/Messages.Designer.cs +++ b/Refhub/Resources/Messages.Designer.cs @@ -213,6 +213,15 @@ public static string ConfirmPassword_Required { } } + /// + /// Looks up a localized string similar to An error occurred while downloading the file. + /// + public static string DownloadError { + get { + return ResourceManager.GetString("DownloadError", resourceCulture); + } + } + /// /// Looks up a localized string similar to The Email is not Valid!. /// @@ -258,6 +267,15 @@ public static string Error_SlugExists { } } + /// + /// Looks up a localized string similar to The requested file was not found. + /// + public static string FileNotFound { + get { + return ResourceManager.GetString("FileNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to FullName is Required. /// @@ -267,6 +285,15 @@ public static string FullNameRequired { } } + /// + /// Looks up a localized string similar to The provided file name is invalid. + /// + public static string InvalidFileName { + get { + return ResourceManager.GetString("InvalidFileName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Keyword found. /// diff --git a/Refhub/Resources/Messages.fa.resx b/Refhub/Resources/Messages.fa.resx index ec92ad70..6f64f1de 100644 --- a/Refhub/Resources/Messages.fa.resx +++ b/Refhub/Resources/Messages.fa.resx @@ -207,6 +207,9 @@ خطایی رخ داده است + + کلید واژه یافت شد. + طول عنوان در مرورگر حداکثر 450 حرف باشد @@ -219,7 +222,13 @@ نام در مرورگر باید حداکثر 450 حرف باشد - - کلید واژه یافت شد. + + فایل مورد نظر پیدا نشد. + + + نام فایل وارد شده نامعتبر است + + + خطا در دانلود فایل رخ داد \ No newline at end of file diff --git a/Refhub/Resources/Messages.resx b/Refhub/Resources/Messages.resx index 95237132..d7d2185d 100644 --- a/Refhub/Resources/Messages.resx +++ b/Refhub/Resources/Messages.resx @@ -222,4 +222,13 @@ The slug must be a maximum of 450 characters. + + The requested file was not found + + + The provided file name is invalid + + + An error occurred while downloading the file + \ No newline at end of file From 778a2de865bbaac8805e6932ac7d44dae47c941b Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:15:21 +0330 Subject: [PATCH 064/106] rename parameter --- Refhub/Areas/Admin/Views/ManageBook/Update.cshtml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml index ec235919..527c337a 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml @@ -47,7 +47,11 @@
- + @if (!string.IsNullOrEmpty(Model.ImagePath)) + { + + } + From 7dbbbe55d0c5e76f26ecd1fcd0925e013a3ac44f Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:15:49 +0330 Subject: [PATCH 065/106] add resource for ImagePath on updateBookvm --- Refhub/Models/Books/UpdateBookVM.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Refhub/Models/Books/UpdateBookVM.cs b/Refhub/Models/Books/UpdateBookVM.cs index 07176e27..39246c4f 100644 --- a/Refhub/Models/Books/UpdateBookVM.cs +++ b/Refhub/Models/Books/UpdateBookVM.cs @@ -28,8 +28,13 @@ public class UpdateBookVM public string? FilePath { get; set; } public IFormFile? Image { get; set; } + [Url( +ErrorMessageResourceType = typeof(Messages), +ErrorMessageResourceName = nameof(Messages.Book_ImagePathMustBeUrl) + )] public string? ImagePath { get; set; } + public string? UserId { get; set; } // Foreign Key [Required(ErrorMessageResourceType = typeof(Messages), From 9c07f536d19f09b2b4a638c55a623fc854fe0d7b Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:16:10 +0330 Subject: [PATCH 066/106] add message Book_ImagePathMustBeUrl --- Refhub/Resources/Messages.Designer.cs | 9 +++++++++ Refhub/Resources/Messages.fa.resx | 5 ++++- Refhub/Resources/Messages.resx | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Refhub/Resources/Messages.Designer.cs b/Refhub/Resources/Messages.Designer.cs index 13efc7d4..48dd1b56 100644 --- a/Refhub/Resources/Messages.Designer.cs +++ b/Refhub/Resources/Messages.Designer.cs @@ -132,6 +132,15 @@ public static string Book_FileRequired { } } + /// + /// Looks up a localized string similar to The book image path must be a valid URL.. + /// + public static string Book_ImagePathMustBeUrl { + get { + return ResourceManager.GetString("Book_ImagePathMustBeUrl", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select a Image. /// diff --git a/Refhub/Resources/Messages.fa.resx b/Refhub/Resources/Messages.fa.resx index 6f64f1de..9bf3af38 100644 --- a/Refhub/Resources/Messages.fa.resx +++ b/Refhub/Resources/Messages.fa.resx @@ -208,7 +208,7 @@ خطایی رخ داده است - کلید واژه یافت شد. + کلید واژه از قبل وجود دارد طول عنوان در مرورگر حداکثر 450 حرف باشد @@ -231,4 +231,7 @@ خطا در دانلود فایل رخ داد + + مسیر تصویر کتاب باید یک URL معتبر باشد + \ No newline at end of file diff --git a/Refhub/Resources/Messages.resx b/Refhub/Resources/Messages.resx index d7d2185d..35c680e0 100644 --- a/Refhub/Resources/Messages.resx +++ b/Refhub/Resources/Messages.resx @@ -231,4 +231,7 @@ An error occurred while downloading the file + + The book image path must be a valid URL. + \ No newline at end of file From 49c79bcd003b28d8216753a63f0e3d9107b4f2af Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:16:31 +0330 Subject: [PATCH 067/106] fix name action DownloadFile --- Refhub/Views/Book/BookDetails.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Views/Book/BookDetails.cshtml b/Refhub/Views/Book/BookDetails.cshtml index 1831bbdf..d1a11888 100644 --- a/Refhub/Views/Book/BookDetails.cshtml +++ b/Refhub/Views/Book/BookDetails.cshtml @@ -74,7 +74,7 @@ @if (!string.IsNullOrEmpty(Model.FilePath)) { } else From 732fda8f62e895e429afa9b32e4c07c0ca027eda Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 10:26:28 +0330 Subject: [PATCH 068/106] update resource message for ImagePath updatebookVm --- Refhub/Models/Books/UpdateBookVM.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Refhub/Models/Books/UpdateBookVM.cs b/Refhub/Models/Books/UpdateBookVM.cs index 39246c4f..d1006613 100644 --- a/Refhub/Models/Books/UpdateBookVM.cs +++ b/Refhub/Models/Books/UpdateBookVM.cs @@ -14,8 +14,8 @@ public class UpdateBookVM [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.Book_SlugRequired))] - - [MaxLength(450,ErrorMessageResourceType = typeof(Messages), + + [MaxLength(450, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.Book_SlugMaxLength))] public string Slug { get; set; } @@ -28,10 +28,8 @@ public class UpdateBookVM public string? FilePath { get; set; } public IFormFile? Image { get; set; } - [Url( -ErrorMessageResourceType = typeof(Messages), -ErrorMessageResourceName = nameof(Messages.Book_ImagePathMustBeUrl) - )] + [Url(ErrorMessageResourceType = typeof(Messages), +ErrorMessageResourceName = nameof(Messages.Book_ImagePathMustBeUrl))] public string? ImagePath { get; set; } From 90724af49862df7513c4956f4a740b3bba2ec651 Mon Sep 17 00:00:00 2001 From: Hootan Hemmati Date: Tue, 15 Jul 2025 10:53:42 +0330 Subject: [PATCH 069/106] Update Refhub/Tools/Static/BucketNameStatic.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Refhub/Tools/Static/BucketNameStatic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Tools/Static/BucketNameStatic.cs b/Refhub/Tools/Static/BucketNameStatic.cs index 8dba74b3..f6514da7 100644 --- a/Refhub/Tools/Static/BucketNameStatic.cs +++ b/Refhub/Tools/Static/BucketNameStatic.cs @@ -12,7 +12,7 @@ public static string GetName(BucketNames bucketNames) BucketNames.BookImages => nameof(BucketNames.BookImages), - _ => throw new ArgumentOutOfRangeException(nameof(bucketNames), $"Unsupported directory type: {bucketNames}") + _ => throw new ArgumentOutOfRangeException(nameof(bucketNames), $"Unsupported bucket name: {bucketNames}") }; } } From 3e22c4198d0e71cf447dac2d8d0bcf27631c5ec3 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:27:09 +0330 Subject: [PATCH 070/106] use condition for imagePath --- .../Components/Views/Home/BestBookView.cshtml | 5 +- .../Components/Views/Home/LastBookView.cshtml | 130 +++++++++--------- Refhub/Views/Book/List.cshtml | 7 +- 3 files changed, 75 insertions(+), 67 deletions(-) diff --git a/Refhub/Components/Views/Home/BestBookView.cshtml b/Refhub/Components/Views/Home/BestBookView.cshtml index d7d348e1..cfb7a2bb 100644 --- a/Refhub/Components/Views/Home/BestBookView.cshtml +++ b/Refhub/Components/Views/Home/BestBookView.cshtml @@ -21,7 +21,10 @@
- @item.Title + @if (!string.IsNullOrEmpty(item.ImagePath)) + { + @item.Title + }
diff --git a/Refhub/Components/Views/Home/LastBookView.cshtml b/Refhub/Components/Views/Home/LastBookView.cshtml index 8cfaa488..c531cecc 100644 --- a/Refhub/Components/Views/Home/LastBookView.cshtml +++ b/Refhub/Components/Views/Home/LastBookView.cshtml @@ -3,70 +3,72 @@
-
- -
-
-

اخرین کتال ها

-

هر موضوعی را در هر زمان مطالعه کنید. هزاران کتاب آموزشی را با کمترین قیمت جستجو کنید!

-
-
+
+ +
+
+

اخرین کتال ها

+

هر موضوعی را در هر زمان مطالعه کنید. هزاران کتاب آموزشی را با کمترین قیمت جستجو کنید!

+
+
- -
- -
-
- - @foreach (var item in Model) - { - -
-
- - @item.Title - -
- - - -
@item.Title
- @*

با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک

*@ - -
    -
  • -
  • -
  • -
  • -
  • -
  • 4.0/5.0
  • -
-
- - -
-
- - } - - - -
-
- - - -
- -
+ +
+ +
+
+ + @foreach (var item in Model) + { + +
+
+ + @if (!string.IsNullOrEmpty(item.ImagePath)) + { + @item.Title + } + +
+ + + +
@item.Title
+ @*

با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک

*@ + +
    +
  • +
  • +
  • +
  • +
  • +
  • 4.0/5.0
  • +
+
+ + +
+
+ + } + + + +
+
+ + +
+ +
\ No newline at end of file diff --git a/Refhub/Views/Book/List.cshtml b/Refhub/Views/Book/List.cshtml index 8645791e..ecdf0ed3 100644 --- a/Refhub/Views/Book/List.cshtml +++ b/Refhub/Views/Book/List.cshtml @@ -100,8 +100,11 @@ Page content START --> From caa44e770be85d44157a824e932be61f6ed904eb Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:27:36 +0330 Subject: [PATCH 071/106] update AWSSDK.S3 4.0.4.2 --- Refhub/Refhub.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index e77b7974..e6c22627 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -13,7 +13,7 @@ - + From e9c7d763f454fe4410acf3520d3a6905d28c65f5 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:27:48 +0330 Subject: [PATCH 072/106] use FileDownloadException --- Refhub/Controllers/BookController.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 92150300..f69269de 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -5,6 +5,7 @@ using Refhub.Models.Enums; using Refhub.Service.Implement; using Refhub.Service.Interface; +using Refhub.Tools.Exceptions; using Refhub.Tools.Static; namespace Refhub.Controllers; @@ -48,14 +49,15 @@ public async Task DownloadFile(string fileName, CancellationToken return File(stream, contentType, fileName); } - catch (AmazonS3Exception s3Ex) + catch (FileDownloadException s3Ex) { - _logger.LogError(s3Ex, "خطا در دانلود فایل از S3: {Message}", s3Ex.Message); + _logger.LogError(s3Ex, "Error downloading file from S3: {Message}", s3Ex.Message); + return NotFound(_messageService.Get("FileNotFound")); } catch (Exception ex) { - _logger.LogError(ex, "خطای پیش‌بینی‌نشده هنگام دانلود فایل."); + _logger.LogError(ex, "Unexpected error occurred while downloading the file."); return StatusCode(500, _messageService.Get("DownloadError")); } } From f762f4cb02771fd18e3e5fecb75d151a75c81680 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:28:07 +0330 Subject: [PATCH 073/106] update BookAuthorConfiguration --- Refhub/Data/Configuration/BookAuthorConfiguration.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Refhub/Data/Configuration/BookAuthorConfiguration.cs b/Refhub/Data/Configuration/BookAuthorConfiguration.cs index fd59d582..f3e10d44 100644 --- a/Refhub/Data/Configuration/BookAuthorConfiguration.cs +++ b/Refhub/Data/Configuration/BookAuthorConfiguration.cs @@ -10,7 +10,10 @@ public void Configure(EntityTypeBuilder builder) { builder.HasKey(ba => new { ba.BookId, ba.AuthorId }); - + builder.HasOne(ba => ba.Book) + .WithMany(b => b.BookAuthors) + .HasForeignKey(ba => ba.BookId) + .OnDelete(DeleteBehavior.Cascade); builder.HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) From 4f5c55a6a97a820defec9a9e4a05bb61843a5a37 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:28:23 +0330 Subject: [PATCH 074/106] add sqlQuery --- ...250612083534_AddUniqueIndexToBookSlugAndModifyType.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs b/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs index cc760785..a123764f 100644 --- a/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs +++ b/Refhub/Migrations/20250612083534_AddUniqueIndexToBookSlugAndModifyType.cs @@ -17,9 +17,12 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, oldClrType: typeof(string), oldType: "nvarchar(max)"); - migrationBuilder.DropIndex( - name: "IX_Books_Slug", - table: "Books"); + + migrationBuilder.Sql(@" + IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Books_Slug' AND object_id = OBJECT_ID('Books')) + BEGIN + DROP INDEX IX_Books_Slug ON Books; + END"); migrationBuilder.CreateIndex( name: "IX_Books_Slug", table: "Books", From 354aa350a2d38b3667be6e5ece017b9c758ffb8e Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:28:38 +0330 Subject: [PATCH 075/106] convert englishMessage --- Refhub/Service/Implement/S3FileUploaderService.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 9bda2867..b16e3d80 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -10,6 +10,7 @@ namespace Refhub.Service.Implement using Amazon.S3; using Amazon.S3.Model; using Refhub.Models.Enums; + using Refhub.Resources; using Refhub.Tools.Exceptions; using System.Threading; @@ -53,7 +54,7 @@ private string GetKey(string realUrl, string bucketName) return realUrl.Substring(prefix.Length); } - throw new ArgumentException("آدرس مورد نظر با پیشوند تعیین‌شده شروع نمی‌شود."); + throw new ArgumentException("The provided URL does not start with the expected prefix."); } @@ -61,7 +62,7 @@ private string GetKey(string realUrl, string bucketName) public async Task UploadFile(IFormFile file, string directoryName, string name) { var bucketName = directoryName; - var key = $"{name.Replace(" ", "-")}{Path.GetExtension(file.FileName)}"; + var key = $"{name.Replace(" ", "-")}_{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; using var stream = file.OpenReadStream(); var request = new PutObjectRequest @@ -103,15 +104,12 @@ public async Task DownloadFileAsync(string fileName, CancellationToken c try { using var response = await _s3Client.GetObjectAsync(request, ct); - var memoryStream = new MemoryStream(); - await response.ResponseStream.CopyToAsync(memoryStream, ct); - memoryStream.Position = 0; // مهم! برای اینکه موقع Return از ابتدا خونده بشه - return memoryStream; + return response.ResponseStream; } catch (AmazonS3Exception ex) { // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود - throw new FileDownloadException("خطا در دانلود فایل از S3", ex); + throw new FileDownloadException("Error downloading file from S3", ex); } } From 7963303a57ca85b7883a75f6862361196fd78e33 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:36:13 +0330 Subject: [PATCH 076/106] update S3FileUploaderService for download by MemoryStream --- Refhub/Service/Implement/S3FileUploaderService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index b16e3d80..bf6096a2 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -104,7 +104,11 @@ public async Task DownloadFileAsync(string fileName, CancellationToken c try { using var response = await _s3Client.GetObjectAsync(request, ct); - return response.ResponseStream; + var ms = new MemoryStream(); + await response.ResponseStream.CopyToAsync(ms, ct); + ms.Position = 0; + + return ms; } catch (AmazonS3Exception ex) { From e3a681a9075b13449daacaf7cbdb32c439c43ff8 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 11:52:54 +0330 Subject: [PATCH 077/106] update --- .../Areas/Admin/Views/ManageBook/Index.cshtml | 115 ++++++++++-------- Refhub/Service/Implement/BookService.cs | 5 +- .../Implement/S3FileUploaderService.cs | 2 + Refhub/appsettings.json | 26 ++-- 4 files changed, 83 insertions(+), 65 deletions(-) diff --git a/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml index 61f21505..7c28c408 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Index.cshtml @@ -2,70 +2,77 @@ @using Refhub.Tools.ExtensionMethod @model IEnumerable @{ - Layout = "_AdminLayout"; - ViewBag.TitleHeaderPage = "مدیریت کتاب ها"; + Layout = "_AdminLayout"; + ViewBag.TitleHeaderPage = "مدیریت کتاب ها"; } @{ - var searchText = Context.Request.Query["searchtext"].ToString(); + var searchText = Context.Request.Query["searchtext"].ToString(); }
-
+
- -
-
-
-
+ +
+
+
+
- ساخت کتاب جدید -
-
-
- -
- -
-
-
-
+ ساخت کتاب جدید +
+
+
+ +
+ +
+
+
+
-
- -
- - - - - - - + + +
+
#نام کتابعملیات
+ + + + + + - @foreach (var item in Model) - { - - - - - - } - + @foreach (var item in Model) + { + + + + + + } - -
#نام کتابعملیات
@item.Title - حذف - ویرایش -
+ + @if (!string.IsNullOrEmpty(item.ImagePath)) + { + + } + + @item.Title + حذف + ویرایش +
-
- -
- -
-
-
+ + + +
+ +
+ +
+
+
@* @section Scripts { diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index 82efc17d..87a034e7 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -93,10 +93,7 @@ public async Task> GetBestBooksAsync(CancellationToken c public async Task CreateAnotherAsync(string fullname, string slug, CancellationToken ct) { - if (await context.Authors.AnyAsync(a => a.Slug == slug, ct)) - { - return false; - } + var author = new Author { FullName = fullname, Slug = slug }; await context.Authors.AddAsync(author, ct); diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index bf6096a2..1745b182 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -81,6 +81,8 @@ public async Task UploadFile(IFormFile file, string directoryName, strin public async Task DeleteFile(string realUrl, string? bucketName) { + if (string.IsNullOrWhiteSpace(bucketName)) + throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucketName)); var key = GetKey(realUrl, bucketName); diff --git a/Refhub/appsettings.json b/Refhub/appsettings.json index 23160a4d..413e9c19 100644 --- a/Refhub/appsettings.json +++ b/Refhub/appsettings.json @@ -1,9 +1,21 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" + "ConnectionStrings": { + "DefaultConnection": "Data Source=.;Initial Catalog=RefHubDB;Integrated Security=True;Multiple Active Result Sets=True;Trust Server Certificate=True" + }, + "AWS": { + "S3": { + "Region": "s3.ir-thr-at1.arvanstorage.ir", + "AccessKey": "b00b79c3-6201-4d3b-9fde-62053d491ba3", + "SecretKey": "e6d3f6d22238420f66d60436ad8c16cb298e5410a66fb7d60081d90b673a06c7", + //"BucketName": "test12333555", + "ServiceURL": "https://s3.ir-thr-at1.arvanstorage.ir" + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" } From 07c5fd5a47689fec5006b2eb7baa57bd44eb19fc Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 15 Jul 2025 12:10:04 +0330 Subject: [PATCH 078/106] update --- Refhub/appsettings.json | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Refhub/appsettings.json b/Refhub/appsettings.json index 413e9c19..23160a4d 100644 --- a/Refhub/appsettings.json +++ b/Refhub/appsettings.json @@ -1,21 +1,9 @@ { - "ConnectionStrings": { - "DefaultConnection": "Data Source=.;Initial Catalog=RefHubDB;Integrated Security=True;Multiple Active Result Sets=True;Trust Server Certificate=True" - }, - "AWS": { - "S3": { - "Region": "s3.ir-thr-at1.arvanstorage.ir", - "AccessKey": "b00b79c3-6201-4d3b-9fde-62053d491ba3", - "SecretKey": "e6d3f6d22238420f66d60436ad8c16cb298e5410a66fb7d60081d90b673a06c7", - //"BucketName": "test12333555", - "ServiceURL": "https://s3.ir-thr-at1.arvanstorage.ir" - } - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" } From 74cdaf0d53d1e35c0c2c8c4a7e147372aecedf5a Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:11:57 +0330 Subject: [PATCH 079/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20resolve=20f?= =?UTF-8?q?ile=20download=20issues=20and=20improve=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix invalid file name check for download functionality - enhance file download process with url parameter - improve error handling and logging during file downloads --- Refhub/Controllers/BookController.cs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index f69269de..fe3f0cf4 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -10,7 +10,7 @@ namespace Refhub.Controllers; -public class BookController(IBookService bookService,IFileUploaderService _s3FileUploaderService, ILogger _logger, IMessageService _messageService) : Controller +public class BookController(IBookService bookService, IFileUploaderService s3FileUploaderService, ILogger logger, IMessageService messageService) : Controller { [HttpGet("BookDetails/{slug}")] public async Task BookDetails(string slug, CancellationToken ct) @@ -28,17 +28,21 @@ public async Task BookDetails(string slug, CancellationToken ct) [Authorize] - [HttpGet("download/{fileName}")] - public async Task DownloadFile(string fileName, CancellationToken ct) + [HttpGet("download")] + [HttpGet("download")] + public async Task DownloadFile([FromQuery] string fileUrl, CancellationToken ct) { try { - if (string.IsNullOrWhiteSpace(fileName) || fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + if (string.IsNullOrWhiteSpace(fileUrl) || !Uri.TryCreate(fileUrl, UriKind.Absolute, out _)) { - return NotFound(_messageService.Get("InvalidFileName")); + return NotFound(messageService.Get("InvalidFileName")); } // دریافت فایل از S3 - var stream = await _s3FileUploaderService.DownloadFileAsync(fileName, ct, BucketNameStatic.GetName(BucketNames.BookPdf)); + var stream = await s3FileUploaderService.DownloadFileAsync(fileUrl, ct, BucketNameStatic.GetName(BucketNames.BookPdf)); + + // The file name for the user should be extracted from the URL + var fileName = Path.GetFileName(new Uri(fileUrl).AbsolutePath); // تعیین نوع فایل با توجه به پسوند var contentTypeProvider = new FileExtensionContentTypeProvider(); @@ -51,14 +55,14 @@ public async Task DownloadFile(string fileName, CancellationToken } catch (FileDownloadException s3Ex) { - _logger.LogError(s3Ex, "Error downloading file from S3: {Message}", s3Ex.Message); + logger.LogError(s3Ex, "Error downloading file from S3: {Message}", s3Ex.Message); - return NotFound(_messageService.Get("FileNotFound")); + return NotFound(messageService.Get("FileNotFound")); } catch (Exception ex) { - _logger.LogError(ex, "Unexpected error occurred while downloading the file."); - return StatusCode(500, _messageService.Get("DownloadError")); + logger.LogError(ex, "Unexpected error occurred while downloading the file."); + return StatusCode(500,messageService.Get("DownloadError")); } } From 69f2998a59da2c16df9a0fecea8e55eba446cd7d Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:12:01 +0330 Subject: [PATCH 080/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20prevent=20d?= =?UTF-8?q?uplicate=20author=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check for existing author slug before creating a new author - return false if the slug already exists --- Refhub/Service/Implement/BookService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index 87a034e7..82efc17d 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -93,7 +93,10 @@ public async Task> GetBestBooksAsync(CancellationToken c public async Task CreateAnotherAsync(string fullname, string slug, CancellationToken ct) { - + if (await context.Authors.AnyAsync(a => a.Slug == slug, ct)) + { + return false; + } var author = new Author { FullName = fullname, Slug = slug }; await context.Authors.AddAsync(author, ct); From 1bd1c6edd447e062a009a040b95462e8f4793db0 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:12:05 +0330 Subject: [PATCH 081/106] =?UTF-8?q?=F0=9F=90=9B=20fix(file):=20handle=20fi?= =?UTF-8?q?le=20not=20found=20exception=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change exception from FileNotFoundException to FileDownloadException with inner exception - improve error handling when file does not exist during download --- Refhub/Service/Implement/LocalFileUploaderService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index 88f6515d..350e705c 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -1,4 +1,5 @@ using Refhub.Service.Interface; +using Refhub.Tools.Exceptions; namespace Refhub.Service.Implement; @@ -43,18 +44,18 @@ public Task DeleteFile(string realUrl, string bucketName) return Task.CompletedTask; } - public Task DownloadFileAsync(string fileName, CancellationToken ct, string? bucketName) + public Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName) { - string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileName.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); + string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileUrl.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); if (!File.Exists(fullPath)) { - throw new FileNotFoundException("فایل پیدا نشد", fileName); + throw new FileDownloadException($"File not found at {fileUrl}", new FileNotFoundException()); } Stream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); return Task.FromResult(fileStream); } - + } From 27fe515c006f65d564d09797379364d1f7a08477 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:12:09 +0330 Subject: [PATCH 082/106] =?UTF-8?q?=F0=9F=90=9B=20fix(file):=20correct=20p?= =?UTF-8?q?arameter=20name=20in=20DownloadFileAsync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - correct the parameter name from fileName to fileUrl in the DownloadFileAsync method --- Refhub/Service/Interface/IFileUploaderService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index a9749e64..0783889b 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -4,5 +4,5 @@ public interface IFileUploaderService { Task UploadFile(IFormFile file, string directoryName, string name); Task DeleteFile(string realUrl, string bucketName); - Task DownloadFileAsync(string fileName,CancellationToken ct, string? bucketName); + Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName); } From 13f46cae8ac6dc4a9ddf7d71ea493b82bf81eaff Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:12:13 +0330 Subject: [PATCH 083/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20fix=20downl?= =?UTF-8?q?oad=20file=20url=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix the parameter name in the download url from fileName to fileUrl --- Refhub/Views/Book/BookDetails.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Views/Book/BookDetails.cshtml b/Refhub/Views/Book/BookDetails.cshtml index d1a11888..f9e189a9 100644 --- a/Refhub/Views/Book/BookDetails.cshtml +++ b/Refhub/Views/Book/BookDetails.cshtml @@ -74,7 +74,7 @@ @if (!string.IsNullOrEmpty(Model.FilePath)) { } else From 1db68a4ca3de70e35fcbf413bb01489f469bd492 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:15:59 +0330 Subject: [PATCH 084/106] =?UTF-8?q?=F0=9F=90=9B=20fix(uploader):=20correct?= =?UTF-8?q?=20file=20path=20for=20local=20uploader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix the file path returned by the local file uploader service - the path was only returning the file name, not the full path - now returns the full path, combining directory and filename --- Refhub/Service/Implement/LocalFileUploaderService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs index 350e705c..1e160c7b 100644 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ b/Refhub/Service/Implement/LocalFileUploaderService.cs @@ -28,8 +28,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin { await file.CopyToAsync(stream); } - - return fileName; + return Path.Combine(directoryName, fileName).Replace(Path.DirectorySeparatorChar, '/'); } public Task DeleteFile(string realUrl, string bucketName) From 64324dcdac3d50d1bda87f3e6e70172bba952bbf Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:19:18 +0330 Subject: [PATCH 085/106] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(upload):?= =?UTF-8?q?=20remove=20local=20file=20uploader=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove local file uploader service and related files --- .../Implement/LocalFileUploaderService.cs | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 Refhub/Service/Implement/LocalFileUploaderService.cs diff --git a/Refhub/Service/Implement/LocalFileUploaderService.cs b/Refhub/Service/Implement/LocalFileUploaderService.cs deleted file mode 100644 index 1e160c7b..00000000 --- a/Refhub/Service/Implement/LocalFileUploaderService.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Refhub.Service.Interface; -using Refhub.Tools.Exceptions; - -namespace Refhub.Service.Implement; - -public class LocalFileUploaderService : IFileUploaderService -{ - private readonly string _rootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Files"); - - public async Task UploadFile(IFormFile file, string directoryName, string name) - { - // ایجاد مسیر دایرکتوری نهایی - string targetDirectory = Path.Combine(_rootPath, directoryName); - - // ایجاد پوشه‌ها در صورت نبود - if (!Directory.Exists(targetDirectory)) - { - Directory.CreateDirectory(targetDirectory); - } - - // تمیزسازی و ساخت نام یکتا - name = name.Replace(" ", "-") + "_" + Path.GetRandomFileName().Replace(".", ""); - string fileName = name + Path.GetExtension(file.FileName); - string fullPath = Path.Combine(targetDirectory, fileName); - - // ذخیره فایل به صورت async - using (Stream stream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) - { - await file.CopyToAsync(stream); - } - return Path.Combine(directoryName, fileName).Replace(Path.DirectorySeparatorChar, '/'); - } - - public Task DeleteFile(string realUrl, string bucketName) - { - string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", realUrl.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); - - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - - return Task.CompletedTask; - } - - public Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName) - { - string fullPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", fileUrl.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString())); - - if (!File.Exists(fullPath)) - { - throw new FileDownloadException($"File not found at {fileUrl}", new FileNotFoundException()); - } - - Stream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); - return Task.FromResult(fileStream); - } - - -} From 3b0e2392aefdc8f1ce8b3f3de58d0b45cea9d1c5 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:33:17 +0330 Subject: [PATCH 086/106] =?UTF-8?q?=F0=9F=90=9B=20fix(s3):=20correct=20fil?= =?UTF-8?q?e=20download=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix the DownloadFileAsync method to correctly retrieve files from S3 - use the GetKey method to extract the key from the file URL - ensure bucket name is not null or empty --- Refhub/Service/Implement/S3FileUploaderService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 1745b182..1cfe0f70 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -95,12 +95,16 @@ public async Task DeleteFile(string realUrl, string? bucketName) await _s3Client.DeleteObjectAsync(request); } - public async Task DownloadFileAsync(string fileName, CancellationToken ct, string? bucketName) + public async Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName) { + if (string.IsNullOrWhiteSpace(bucketName)) + throw new ArgumentException("Bucket name cannot be null or empty.", nameof(bucketName)); + + var key = GetKey(fileUrl, bucketName); var request = new GetObjectRequest { BucketName = bucketName.ToLower(), - Key = fileName // مثلاً: "images/profile.jpg" + Key = key }; try From 7d52d825fc527908d96144be326a75fe78bcffef Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 10:43:22 +0330 Subject: [PATCH 087/106] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20perf(book):=20opti?= =?UTF-8?q?mize=20book=20retrieval=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove unnecessary inclusion of BookAuthors navigation property - improve query performance by fetching only required data --- Refhub/Service/Implement/BookService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index 82efc17d..41980349 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -257,8 +257,7 @@ public async Task DeleteBookAsync(int bookId, CancellationToken ct) try { var book = await context.Books - .Include(b => b.BookAuthors) - .FirstOrDefaultAsync(b => b.Id == bookId, ct); + .FirstOrDefaultAsync(b => b.Id == bookId, ct); if (book == null) { return false; From 57ab437ff87d9fe859e3b4b90f8646182dc790de Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 11:00:44 +0330 Subject: [PATCH 088/106] =?UTF-8?q?=F0=9F=90=9B=20fix(data):=20cascade=20d?= =?UTF-8?q?elete=20behavior=20in=20BookAuthorConfiguration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - configure cascade delete behavior for BookAuthor entity - prevent orphaned records when deleting books or authors --- Refhub/Data/Configuration/BookAuthorConfiguration.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Refhub/Data/Configuration/BookAuthorConfiguration.cs b/Refhub/Data/Configuration/BookAuthorConfiguration.cs index f3e10d44..6ac24d61 100644 --- a/Refhub/Data/Configuration/BookAuthorConfiguration.cs +++ b/Refhub/Data/Configuration/BookAuthorConfiguration.cs @@ -11,13 +11,14 @@ public void Configure(EntityTypeBuilder builder) builder.HasKey(ba => new { ba.BookId, ba.AuthorId }); builder.HasOne(ba => ba.Book) - .WithMany(b => b.BookAuthors) - .HasForeignKey(ba => ba.BookId) - .OnDelete(DeleteBehavior.Cascade); + .WithMany(b => b.BookAuthors) + .HasForeignKey(ba => ba.BookId) + .OnDelete(DeleteBehavior.Cascade); builder.HasOne(ba => ba.Author) .WithMany(a => a.BookAuthors) .HasForeignKey(ba => ba.AuthorId) .OnDelete(DeleteBehavior.Cascade); + } } From a89a44f9d35b80ae1140aa5c68027faab086e8ba Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 11:00:49 +0330 Subject: [PATCH 089/106] =?UTF-8?q?=F0=9F=90=9B=20fix(data):=20cascade=20d?= =?UTF-8?q?elete=20related=20books?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix issue where related books were not being deleted when a book was deleted - configure cascade delete behavior for both RelatedTo and RelatedFrom relationships --- Refhub/Data/Configuration/BookConfiguration.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Refhub/Data/Configuration/BookConfiguration.cs b/Refhub/Data/Configuration/BookConfiguration.cs index fc4bde9a..6e4ed11f 100644 --- a/Refhub/Data/Configuration/BookConfiguration.cs +++ b/Refhub/Data/Configuration/BookConfiguration.cs @@ -27,10 +27,13 @@ public void Configure(EntityTypeBuilder builder) .HasForeignKey(ba => ba.BookId); builder.HasMany(b => b.RelatedTo).WithOne(rt => rt.Book) - .HasForeignKey(bt => bt.BookId); + .HasForeignKey(bt => bt.BookId).OnDelete(DeleteBehavior.Cascade); ; + + + builder.HasMany(b => b.RelatedFrom).WithOne(rf => rf.RelatedBook) - .HasForeignKey(rf => rf.RelatedBookId); + .HasForeignKey(rf => rf.RelatedBookId).OnDelete(DeleteBehavior.Cascade); ; builder.HasOne(b => b.User).WithMany(rf => rf.Books) From 7436affd6a702cd8fc0e33bd02234b3220b50fad Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 11:00:53 +0330 Subject: [PATCH 090/106] =?UTF-8?q?=F0=9F=90=9B=20fix(s3):=20enforce=20non?= =?UTF-8?q?-nullable=20bucket=20name=20for=20s3=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ensure bucketName parameter is not nullable in DeleteFile and DownloadFileAsync methods - throw ArgumentException if bucketName is null or empty --- Refhub/Service/Implement/S3FileUploaderService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 1cfe0f70..f62848d0 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -79,7 +79,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin return GenerateS3Url(key, bucketName); } - public async Task DeleteFile(string realUrl, string? bucketName) + public async Task DeleteFile(string realUrl, string bucketName) { if (string.IsNullOrWhiteSpace(bucketName)) throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucketName)); @@ -95,7 +95,7 @@ public async Task DeleteFile(string realUrl, string? bucketName) await _s3Client.DeleteObjectAsync(request); } - public async Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName) + public async Task DownloadFileAsync(string fileUrl, CancellationToken ct, string bucketName) { if (string.IsNullOrWhiteSpace(bucketName)) throw new ArgumentException("Bucket name cannot be null or empty.", nameof(bucketName)); From 6544595165ca9ca518943202eeff54b381cd23ea Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Fri, 18 Jul 2025 11:00:57 +0330 Subject: [PATCH 091/106] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(fileUploa?= =?UTF-8?q?der):=20standardize=20parameter=20naming=20for=20file=20storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename directoryName parameter to bucketName for clarity - ensure consistency in file storage parameter naming --- Refhub/Service/Interface/IFileUploaderService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Refhub/Service/Interface/IFileUploaderService.cs b/Refhub/Service/Interface/IFileUploaderService.cs index 0783889b..3b6a4e6c 100644 --- a/Refhub/Service/Interface/IFileUploaderService.cs +++ b/Refhub/Service/Interface/IFileUploaderService.cs @@ -2,7 +2,7 @@ public interface IFileUploaderService { - Task UploadFile(IFormFile file, string directoryName, string name); + Task UploadFile(IFormFile file, string bucketName, string name); Task DeleteFile(string realUrl, string bucketName); - Task DownloadFileAsync(string fileUrl, CancellationToken ct, string? bucketName); + Task DownloadFileAsync(string fileUrl, CancellationToken ct, string bucketName); } From 075771db4cb4136b90bcb20518143c82cdbb845a Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:14:53 +0330 Subject: [PATCH 092/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20resolve=20d?= =?UTF-8?q?ownload=20link=20issue=20in=20book=20details=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - correct parameter name from 'filePath' to 'fileUrl' in DownloadFile action link - ensure proper file download functionality for related books --- Refhub/Views/Book/BookDetails.cshtml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Refhub/Views/Book/BookDetails.cshtml b/Refhub/Views/Book/BookDetails.cshtml index f9e189a9..029fe632 100644 --- a/Refhub/Views/Book/BookDetails.cshtml +++ b/Refhub/Views/Book/BookDetails.cshtml @@ -74,6 +74,7 @@ @if (!string.IsNullOrEmpty(Model.FilePath)) { } @@ -90,7 +91,7 @@ @if (!string.IsNullOrEmpty(relatedBook.RelatedBook?.FilePath)) {
  • - + دانلود فایل: @(relatedBook.RelatedBook?.Title ?? "بدون عنوان")
  • From 041ee3d8ad8066a993c454fda12bacdb70f681e6 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:14:57 +0330 Subject: [PATCH 093/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20remove=20du?= =?UTF-8?q?plicate=20download=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removed duplicate HttpGet attribute for the download endpoint --- Refhub/Controllers/BookController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index fe3f0cf4..872a5aba 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -29,7 +29,6 @@ public async Task BookDetails(string slug, CancellationToken ct) [Authorize] [HttpGet("download")] - [HttpGet("download")] public async Task DownloadFile([FromQuery] string fileUrl, CancellationToken ct) { try From 91055dd5d9074f266d4e4e2aba6a5fc1aaf2b279 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:15:01 +0330 Subject: [PATCH 094/106] =?UTF-8?q?=F0=9F=90=9B=20fix(s3):=20set=20uploade?= =?UTF-8?q?d=20file=20ACL=20to=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - set uploaded file ACL to private to prevent unauthorized access --- Refhub/Service/Implement/S3FileUploaderService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index f62848d0..75f56ea5 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -72,7 +72,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin InputStream = stream, ContentType = file.ContentType, - CannedACL = S3CannedACL.PublicRead + CannedACL = S3CannedACL.Private }; await _s3Client.PutObjectAsync(request); From 824547c8213153156bfda44fae6c82fa9fe9f838 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:16:03 +0330 Subject: [PATCH 095/106] =?UTF-8?q?=F0=9F=90=9B=20fix(data):=20prevent=20c?= =?UTF-8?q?ascade=20delete=20for=20related=20books?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prevent cascade delete when deleting a book to avoid unintended data loss - restrict delete behavior for both RelatedTo and RelatedFrom relationships --- Refhub/Data/Configuration/BookConfiguration.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Refhub/Data/Configuration/BookConfiguration.cs b/Refhub/Data/Configuration/BookConfiguration.cs index 6e4ed11f..4b2a00d2 100644 --- a/Refhub/Data/Configuration/BookConfiguration.cs +++ b/Refhub/Data/Configuration/BookConfiguration.cs @@ -26,14 +26,15 @@ public void Configure(EntityTypeBuilder builder) builder.HasMany(b => b.BookAuthors).WithOne(ba => ba.Book) .HasForeignKey(ba => ba.BookId); - builder.HasMany(b => b.RelatedTo).WithOne(rt => rt.Book) - .HasForeignKey(bt => bt.BookId).OnDelete(DeleteBehavior.Cascade); ; - + builder.HasMany(b => b.RelatedTo).WithOne(rt => rt.Book) + .HasForeignKey(bt => bt.BookId).OnDelete(DeleteBehavior.Restrict); + builder.HasMany(b => b.RelatedFrom).WithOne(rf => rf.RelatedBook) - .HasForeignKey(rf => rf.RelatedBookId).OnDelete(DeleteBehavior.Cascade); ; + .HasForeignKey(rf => rf.RelatedBookId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne(b => b.User).WithMany(rf => rf.Books) From 34c70870c93c1739766f44f11161d79847d01533 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:22:19 +0330 Subject: [PATCH 096/106] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(s3):=20re?= =?UTF-8?q?move=20unused=20bucket=20name=20assignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove commented-out line assigning bucket name - this line was unnecessary and did not affect functionality --- Refhub/Service/Implement/S3FileUploaderService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 75f56ea5..15c69ed4 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -34,7 +34,6 @@ public S3FileUploaderService(IOptions s3Options) _s3Client = new AmazonS3Client(credentials, config); - //_bucketName = s3Options.Value.BucketName; } private string GenerateS3Url(string key, string bucketName) { From 6a71c2c6e55bd1501d6a72b7bed47889eb48a42c Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:24:30 +0330 Subject: [PATCH 097/106] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(s3):=20re?= =?UTF-8?q?move=20bucket=20name=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove bucket name validation from DeleteFile and DownloadFileAsync methods - simplify the logic --- Refhub/Service/Implement/S3FileUploaderService.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 15c69ed4..f445b094 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -80,8 +80,7 @@ public async Task UploadFile(IFormFile file, string directoryName, strin public async Task DeleteFile(string realUrl, string bucketName) { - if (string.IsNullOrWhiteSpace(bucketName)) - throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucketName)); + var key = GetKey(realUrl, bucketName); @@ -96,8 +95,6 @@ public async Task DeleteFile(string realUrl, string bucketName) public async Task DownloadFileAsync(string fileUrl, CancellationToken ct, string bucketName) { - if (string.IsNullOrWhiteSpace(bucketName)) - throw new ArgumentException("Bucket name cannot be null or empty.", nameof(bucketName)); var key = GetKey(fileUrl, bucketName); var request = new GetObjectRequest From c4c90383f017c6b03e1ba2d81ba81407a1cbcd74 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:33:51 +0330 Subject: [PATCH 098/106] =?UTF-8?q?=F0=9F=92=84=20style(managebook):=20imp?= =?UTF-8?q?rove=20image=20display=20logic=20in=20update=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refine conditional statement for image display - ensure consistent code style for better readability --- Refhub/Areas/Admin/Views/ManageBook/Update.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml index 527c337a..1c32d772 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml @@ -48,9 +48,9 @@
    @if (!string.IsNullOrEmpty(Model.ImagePath)) - { - - } + { + + } From f196cb41381b3b941d411f6a8a4e8054858eb71a Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:34:04 +0330 Subject: [PATCH 099/106] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(upload):?= =?UTF-8?q?=20simplify=20s3=20file=20upload=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove directoryName parameter and use bucketName directly - improve code readability and maintainability --- Refhub/Service/Implement/S3FileUploaderService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index f445b094..8b0b9175 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -58,9 +58,8 @@ private string GetKey(string realUrl, string bucketName) } - public async Task UploadFile(IFormFile file, string directoryName, string name) + public async Task UploadFile(IFormFile file, string bucketName, string name) { - var bucketName = directoryName; var key = $"{name.Replace(" ", "-")}_{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; using var stream = file.OpenReadStream(); From 73eb3685ff589bd16445a049f827493762673de6 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:35:20 +0330 Subject: [PATCH 100/106] =?UTF-8?q?=F0=9F=94=A5=20chore(static):=20remove?= =?UTF-8?q?=20FolderNameStatic=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove FolderNameStatic class - the class is no longer needed --- Refhub/Tools/Static/FolderNameStatic.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Refhub/Tools/Static/FolderNameStatic.cs diff --git a/Refhub/Tools/Static/FolderNameStatic.cs b/Refhub/Tools/Static/FolderNameStatic.cs deleted file mode 100644 index d7068ab5..00000000 --- a/Refhub/Tools/Static/FolderNameStatic.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Refhub.Models.Enums; - -namespace Refhub.Tools.Static; - -public static class FolderNameStatic -{ - public static string GetDirectoryName(DirectoryTypes folder) - { - return folder switch - { - DirectoryTypes.Images => nameof(DirectoryTypes.Images), - DirectoryTypes.Books => nameof(DirectoryTypes.Books), - DirectoryTypes.Files => nameof(DirectoryTypes.Files), - - _ => throw new ArgumentOutOfRangeException(nameof(folder), $"Unsupported directory type: {folder}") - }; - } -} From 0adbe11ca8b054b783d4292010b681008332a5d6 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:42:40 +0330 Subject: [PATCH 101/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20validate=20?= =?UTF-8?q?file=20URL=20before=20download?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add IsValidFileUrl method to check file URL validity - return NotFound if file URL is invalid --- Refhub/Controllers/BookController.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index 872a5aba..b5353040 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -25,7 +25,10 @@ public async Task BookDetails(string slug, CancellationToken ct) return bookDetails == null ? NotFound() : View(bookDetails); } - + private bool IsValidFileUrl(string fileUrl) + { + return !string.IsNullOrWhiteSpace(fileUrl) || !Uri.TryCreate(fileUrl, UriKind.Absolute, out _); + } [Authorize] [HttpGet("download")] @@ -33,10 +36,12 @@ public async Task DownloadFile([FromQuery] string fileUrl, Cancel { try { - if (string.IsNullOrWhiteSpace(fileUrl) || !Uri.TryCreate(fileUrl, UriKind.Absolute, out _)) + if (!IsValidFileUrl(fileUrl)) { return NotFound(messageService.Get("InvalidFileName")); } + + // دریافت فایل از S3 var stream = await s3FileUploaderService.DownloadFileAsync(fileUrl, ct, BucketNameStatic.GetName(BucketNames.BookPdf)); From 51873f0e57a7d3772c431819fa3f8dacbc58fe36 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:42:47 +0330 Subject: [PATCH 102/106] =?UTF-8?q?=E2=9C=A8=20feat(validation):=20add=20v?= =?UTF-8?q?alidation=20scripts=20partial=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - enable client-side validation in admin area - include jquery, jquery-validation, and jquery-validation-unobtrusive libraries --- .../Admin/Views/Shared/_ValidationScriptsPartial.cshtml | 5 +++++ Refhub/Views/Shared/_ValidationScriptsPartial.cshtml | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Refhub/Areas/Admin/Views/Shared/_ValidationScriptsPartial.cshtml diff --git a/Refhub/Areas/Admin/Views/Shared/_ValidationScriptsPartial.cshtml b/Refhub/Areas/Admin/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 00000000..0d42b75e --- /dev/null +++ b/Refhub/Areas/Admin/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,5 @@ + + + + + diff --git a/Refhub/Views/Shared/_ValidationScriptsPartial.cshtml b/Refhub/Views/Shared/_ValidationScriptsPartial.cshtml index 0d42b75e..bc6104a2 100644 --- a/Refhub/Views/Shared/_ValidationScriptsPartial.cshtml +++ b/Refhub/Views/Shared/_ValidationScriptsPartial.cshtml @@ -1,5 +1,4 @@  - From 5ccefd57129ad56429f12015bd6c3c6e1d9841a8 Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:42:52 +0330 Subject: [PATCH 103/106] =?UTF-8?q?=F0=9F=90=9B=20fix(BookService):=20hand?= =?UTF-8?q?le=20exceptions=20during=20author=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add a try-catch block around SaveChangesAsync to catch and log exceptions - rethrow the exception to ensure it's handled upstream --- Refhub/Service/Implement/BookService.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/BookService.cs b/Refhub/Service/Implement/BookService.cs index 41980349..7919071b 100644 --- a/Refhub/Service/Implement/BookService.cs +++ b/Refhub/Service/Implement/BookService.cs @@ -100,7 +100,16 @@ public async Task CreateAnotherAsync(string fullname, string slug, Cancell var author = new Author { FullName = fullname, Slug = slug }; await context.Authors.AddAsync(author, ct); - await context.SaveChangesAsync(ct); + try + { + await context.SaveChangesAsync(ct); + } + catch (Exception ex) + { + // Log the exception or handle it appropriately + Console.WriteLine($"Error saving changes: {ex.Message}"); + throw; + } return true; } From 7880d5aadb526cd443bb380871a0c790176f6f9a Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:43:17 +0330 Subject: [PATCH 104/106] =?UTF-8?q?=E2=9C=A8=20feat(managebook):=20add=20v?= =?UTF-8?q?alidation=20scripts=20partial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement client-side validation for manage book form --- Refhub/Areas/Admin/Views/ManageBook/Update.cshtml | 1 + 1 file changed, 1 insertion(+) diff --git a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml index 1c32d772..6c9cf703 100644 --- a/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml +++ b/Refhub/Areas/Admin/Views/ManageBook/Update.cshtml @@ -76,5 +76,6 @@
    @section Scripts { + } From e7a409b3af7f10cf75638d46317a3cc805942eaf Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:52:59 +0330 Subject: [PATCH 105/106] =?UTF-8?q?=F0=9F=90=9B=20fix(book):=20correct=20f?= =?UTF-8?q?ile=20URL=20validation=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix the condition in IsValidFileUrl to properly validate file URLs - ensure that the file URL is not null or whitespace and is a valid absolute URI --- Refhub/Controllers/BookController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refhub/Controllers/BookController.cs b/Refhub/Controllers/BookController.cs index b5353040..6374ca9c 100644 --- a/Refhub/Controllers/BookController.cs +++ b/Refhub/Controllers/BookController.cs @@ -27,7 +27,7 @@ public async Task BookDetails(string slug, CancellationToken ct) } private bool IsValidFileUrl(string fileUrl) { - return !string.IsNullOrWhiteSpace(fileUrl) || !Uri.TryCreate(fileUrl, UriKind.Absolute, out _); + return !string.IsNullOrWhiteSpace(fileUrl) && Uri.TryCreate(fileUrl, UriKind.Absolute, out _); } [Authorize] From 5edf84cad0bd99e041347e57513029e350328dbe Mon Sep 17 00:00:00 2001 From: abolfazlshs80 Date: Tue, 22 Jul 2025 19:54:31 +0330 Subject: [PATCH 106/106] =?UTF-8?q?=F0=9F=90=9B=20fix(S3FileUploader):=20a?= =?UTF-8?q?dd=20validation=20for=20fileUrl=20and=20bucketName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add validation for fileUrl and bucketName to prevent empty values - throw ArgumentException if fileUrl or bucketName is null or empty --- Refhub/Service/Implement/S3FileUploaderService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Refhub/Service/Implement/S3FileUploaderService.cs b/Refhub/Service/Implement/S3FileUploaderService.cs index 8b0b9175..ff8cab3d 100644 --- a/Refhub/Service/Implement/S3FileUploaderService.cs +++ b/Refhub/Service/Implement/S3FileUploaderService.cs @@ -94,7 +94,11 @@ public async Task DeleteFile(string realUrl, string bucketName) public async Task DownloadFileAsync(string fileUrl, CancellationToken ct, string bucketName) { + if (string.IsNullOrWhiteSpace(fileUrl)) + throw new ArgumentException("File URL cannot be null or empty", nameof(fileUrl)); + if (string.IsNullOrWhiteSpace(bucketName)) + throw new ArgumentException("Bucket name cannot be null or empty", nameof(bucketName)); var key = GetKey(fileUrl, bucketName); var request = new GetObjectRequest { @@ -113,7 +117,6 @@ public async Task DownloadFileAsync(string fileUrl, CancellationToken ct } catch (AmazonS3Exception ex) { - // مثلاً اگر فایل وجود نداشت یا کلید اشتباه بود throw new FileDownloadException("Error downloading file from S3", ex); } }