feat: integrate PYMS payment gateway, add blog/sitepage/image services, local file manager
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m44s

Payment Gateway:
- Add PYMSPaymentService: IPaymentGatewayService via gRPC to PYMS microservice
- Add ZarinPalPaymentService: direct ZarinPal integration (backup)
- Register 'pyms' payment provider in DI ConfigureServices
- Add PYMS proto files (pyms_transaction.proto, pyms_public_messages.proto)
- Fix VerifyDiscountWalletCharge: pass 'OK' as status instead of Authority
- Update appsettings: PaymentProvider=pyms, sandbox mode, merchant ID

Blog System:
- Add BlogCategory, BlogPost, BlogPostImage entities and CQRS
- Add proto files and gRPC services for blog management
- Add Mapster profiles for blog responses

Content Management:
- Add SitePage entity and CQRS for static pages
- Add proto and gRPC service for site pages

Image/File Management:
- Add LocalFileManager with disk storage + base64 serving + FMS fallback
- Add ImagePathResolverInterceptor for gRPC responses
- Add ImageResolverService for explicit image resolution
- Add UploadsController for public file serving with FMS fallback
- Add PaymentCallbackController for discount order payment callbacks

Database:
- Add blog and content entity migrations
- Remove ImagePath MaxLength constraints
- Remove old FileManagementService (replaced by LocalFileManager)
This commit is contained in:
masoodafar-web
2026-02-15 23:01:16 +03:30
parent 5a4e4a960d
commit 2502cbbda2
177 changed files with 16632 additions and 487 deletions
@@ -23,6 +23,267 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("IconName")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("SortOrder")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("IsActive")
.HasDatabaseName("IX_BlogCategories_IsActive");
b.HasIndex("Slug")
.IsUnique()
.HasDatabaseName("IX_BlogCategories_Slug");
b.ToTable("BlogCategories", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<long>("AuthorUserId")
.HasColumnType("bigint");
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("FeaturedImagePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("FeaturedImageThumbnailPath")
.HasColumnType("nvarchar(max)");
b.Property<string>("HtmlContent")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<bool>("IsFeatured")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("PublishedAt")
.HasColumnType("datetime2");
b.Property<DateTime?>("ScheduledPublishAt")
.HasColumnType("datetime2");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<int>("SortOrder")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Summary")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<int>("ViewCount")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.HasKey("Id");
b.HasIndex("AuthorUserId")
.HasDatabaseName("IX_BlogPosts_AuthorUserId");
b.HasIndex("IsFeatured")
.HasDatabaseName("IX_BlogPosts_IsFeatured");
b.HasIndex("PublishedAt")
.HasDatabaseName("IX_BlogPosts_PublishedAt");
b.HasIndex("Slug")
.IsUnique()
.HasDatabaseName("IX_BlogPosts_Slug");
b.HasIndex("Status")
.HasDatabaseName("IX_BlogPosts_Status");
b.HasIndex("Status", "PublishedAt")
.HasDatabaseName("IX_BlogPosts_Status_PublishedAt");
b.ToTable("BlogPosts", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<long>("BlogCategoryId")
.HasColumnType("bigint");
b.Property<long>("BlogPostId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("BlogCategoryId");
b.HasIndex("BlogPostId");
b.ToTable("BlogPostCategories", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<string>("AltText")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<long>("BlogPostId")
.HasColumnType("bigint");
b.Property<string>("Caption")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("ImagePath")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<int>("SortOrder")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<string>("ThumbnailPath")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("BlogPostId");
b.ToTable("BlogPostImages", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<long>("BlogPostId")
.HasColumnType("bigint");
b.Property<long>("TagId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("BlogPostId");
b.HasIndex("TagId");
b.ToTable("BlogPostTags", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b =>
{
b.Property<long>("Id")
@@ -503,6 +764,142 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("AppVersions", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("HeroImagePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("HeroSubtitle")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("HeroTitle")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("MetaDescription")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<string>("PageKey")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("PageKey")
.IsUnique()
.HasDatabaseName("IX_SitePages_PageKey");
b.ToTable("SitePages", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("Created")
.HasColumnType("datetime2");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("ExtraData")
.HasColumnType("nvarchar(max)");
b.Property<string>("HtmlContent")
.HasColumnType("nvarchar(max)");
b.Property<string>("IconName")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("ImagePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageThumbnailPath")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModified")
.HasColumnType("datetime2");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("SectionKey")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<long>("SitePageId")
.HasColumnType("bigint");
b.Property<int>("SortOrder")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.Property<string>("Subtitle")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("SitePageId", "SectionKey")
.HasDatabaseName("IX_SitePageSections_PageId_SectionKey");
b.ToTable("SitePageSections", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b =>
{
b.Property<long>("Id")
@@ -622,8 +1019,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.HasColumnType("nvarchar(1000)");
b.Property<string>("ImagePath")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
@@ -808,8 +1204,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<string>("ImagePath")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
@@ -847,8 +1242,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<string>("ThumbnailPath")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
@@ -925,8 +1319,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<string>("ImagePath")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
@@ -944,8 +1337,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.HasColumnType("int");
b.Property<string>("ThumbnailPath")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasMaxLength(200)
@@ -2767,9 +3159,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<long?>("OrderVATId")
.HasColumnType("bigint");
b.Property<long?>("PackageId")
.HasColumnType("bigint");
@@ -2796,8 +3185,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("OrderVATId");
b.HasIndex("PackageId");
b.HasIndex("TransactionId");
@@ -3167,6 +3554,55 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.ToTable("WeekDefinitions", "CMS");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory")
.WithMany("BlogPostCategories")
.HasForeignKey("BlogCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost")
.WithMany("BlogPostCategories")
.HasForeignKey("BlogPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlogCategory");
b.Navigation("BlogPost");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost")
.WithMany("BlogPostImages")
.HasForeignKey("BlogPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlogPost");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost")
.WithMany("BlogPostTags")
.HasForeignKey("BlogPostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag")
.WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BlogPost");
b.Navigation("Tag");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent")
@@ -3263,6 +3699,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("WeekDefinition");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage")
.WithMany("Sections")
.HasForeignKey("SitePageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SitePage");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction")
@@ -3502,7 +3949,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order")
.WithOne()
.WithOne("OrderVAT")
.HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
@@ -3657,10 +4104,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b =>
{
b.HasOne("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderVAT")
.WithMany()
.HasForeignKey("OrderVATId");
b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package")
.WithMany("UserOrders")
.HasForeignKey("PackageId");
@@ -3681,8 +4124,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("OrderVAT");
b.Navigation("Package");
b.Navigation("Transaction");
@@ -3766,6 +4207,20 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("Wallet");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b =>
{
b.Navigation("BlogPostCategories");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b =>
{
b.Navigation("BlogPostCategories");
b.Navigation("BlogPostImages");
b.Navigation("BlogPostTags");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b =>
{
b.Navigation("Categories");
@@ -3795,6 +4250,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.Navigation("UserCommissionPayouts");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b =>
{
b.Navigation("Sections");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b =>
{
b.Navigation("UserContracts");
@@ -3915,6 +4375,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b =>
{
b.Navigation("FactorDetails");
b.Navigation("OrderVAT");
});
modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b =>