feat: Add SitePageSettings - simplified page management system
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m2s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m2s
- New entities: SitePageSettings + SitePageImage (4 fixed pages) - EF Migration: AddSitePageSettings (SitePageSettings + SitePageImages tables) - Proto: sitepagesettings.proto with 5 RPCs - CQRS: GetPageSettings, GetAllPageSettings, SavePageSettings, SavePageImage, DeletePageImage - gRPC Service: SitePageSettingsService (auto-registered) - Proto NuGet bumped to 0.0.180
This commit is contained in:
@@ -80,6 +80,10 @@ public interface IApplicationDbContext
|
||||
DbSet<SitePage> SitePages { get; }
|
||||
DbSet<SitePageSection> SitePageSections { get; }
|
||||
|
||||
// ============= Site Page Settings (Simplified) =============
|
||||
DbSet<SitePageSettings> SitePageSettingsEntities { get; }
|
||||
DbSet<SitePageImage> SitePageImages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// دسترسی به DatabaseFacade برای اجرای raw SQL و Stored Procedures
|
||||
/// </summary>
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Commands.DeletePageImage;
|
||||
|
||||
// ── Command ──
|
||||
public class DeletePageImageCommand : IRequest<Unit>
|
||||
{
|
||||
public long Id { get; set; }
|
||||
}
|
||||
|
||||
// ── Handler ──
|
||||
public class DeletePageImageCommandHandler : IRequestHandler<DeletePageImageCommand, Unit>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public DeletePageImageCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(DeletePageImageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.SitePageImages
|
||||
.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (entity != null)
|
||||
{
|
||||
entity.IsDeleted = true; // Soft delete
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
using CMSMicroservice.Application.Common.FileManager;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.Content;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Commands.SavePageImage;
|
||||
|
||||
// ── Command ──
|
||||
public class SavePageImageCommand : IRequest<SavePageImageResult>
|
||||
{
|
||||
public long Id { get; set; } // 0 = ایجاد جدید
|
||||
public long SitePageSettingsId { get; set; }
|
||||
public string ImageGroup { get; set; } = default!;
|
||||
public string? Title { get; set; }
|
||||
public string? Subtitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? LinkUrl { get; set; }
|
||||
public string? IconName { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
// Image upload
|
||||
public byte[]? ImageFileBytes { get; set; }
|
||||
public string? ImageFileMime { get; set; }
|
||||
public string? ImageFileName { get; set; }
|
||||
}
|
||||
|
||||
public class SavePageImageResult
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
// ── Handler ──
|
||||
public class SavePageImageCommandHandler : IRequestHandler<SavePageImageCommand, SavePageImageResult>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IFileManager _fileManager;
|
||||
|
||||
public SavePageImageCommandHandler(IApplicationDbContext context, IFileManager fileManager)
|
||||
{
|
||||
_context = context;
|
||||
_fileManager = fileManager;
|
||||
}
|
||||
|
||||
public async Task<SavePageImageResult> Handle(SavePageImageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
SitePageImage entity;
|
||||
|
||||
if (request.Id > 0)
|
||||
{
|
||||
// ویرایش
|
||||
entity = await _context.SitePageImages
|
||||
.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken)
|
||||
?? throw new Exception($"تصویر با شناسه {request.Id} یافت نشد");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ایجاد جدید
|
||||
entity = new SitePageImage
|
||||
{
|
||||
SitePageSettingsId = request.SitePageSettingsId,
|
||||
ImageGroup = request.ImageGroup,
|
||||
ImagePath = string.Empty // placeholder — will be set below
|
||||
};
|
||||
_context.SitePageImages.Add(entity);
|
||||
}
|
||||
|
||||
entity.Title = request.Title;
|
||||
entity.Subtitle = request.Subtitle;
|
||||
entity.Description = request.Description;
|
||||
entity.LinkUrl = request.LinkUrl;
|
||||
entity.IconName = request.IconName;
|
||||
entity.SortOrder = request.SortOrder;
|
||||
entity.IsActive = request.IsActive;
|
||||
|
||||
// آپلود تصویر
|
||||
if (request.ImageFileBytes is { Length: > 0 })
|
||||
{
|
||||
var result = await _fileManager.UploadImageAsync(
|
||||
$"Images/SitePageSettings/{request.ImageGroup}",
|
||||
request.ImageFileBytes,
|
||||
request.ImageFileMime ?? "image/jpeg",
|
||||
request.ImageFileName,
|
||||
cancellationToken);
|
||||
|
||||
entity.ImagePath = result.Main.Path;
|
||||
entity.ThumbnailPath = result.Thumbnail.Path;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new SavePageImageResult { Id = entity.Id, Success = true };
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
using CMSMicroservice.Application.Common.FileManager;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.Content;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Commands.SavePageSettings;
|
||||
|
||||
// ── Command ──
|
||||
public class SavePageSettingsCommand : IRequest<SavePageSettingsResult>
|
||||
{
|
||||
public string PageKey { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? HeroTitle { get; set; }
|
||||
public string? HeroSubtitle { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string? SettingsJson { get; set; }
|
||||
|
||||
// Hero image upload
|
||||
public byte[]? ImageFileBytes { get; set; }
|
||||
public string? ImageFileMime { get; set; }
|
||||
public string? ImageFileName { get; set; }
|
||||
}
|
||||
|
||||
public class SavePageSettingsResult
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
// ── Handler ──
|
||||
public class SavePageSettingsCommandHandler : IRequestHandler<SavePageSettingsCommand, SavePageSettingsResult>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IFileManager _fileManager;
|
||||
|
||||
public SavePageSettingsCommandHandler(IApplicationDbContext context, IFileManager fileManager)
|
||||
{
|
||||
_context = context;
|
||||
_fileManager = fileManager;
|
||||
}
|
||||
|
||||
public async Task<SavePageSettingsResult> Handle(SavePageSettingsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.SitePageSettingsEntities
|
||||
.FirstOrDefaultAsync(x => x.PageKey == request.PageKey && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
// ایجاد صفحه جدید (فقط برای seed اولیه — صفحات ثابت هستند)
|
||||
entity = new SitePageSettings
|
||||
{
|
||||
PageKey = request.PageKey,
|
||||
};
|
||||
_context.SitePageSettingsEntities.Add(entity);
|
||||
}
|
||||
|
||||
entity.Title = request.Title;
|
||||
entity.MetaDescription = request.MetaDescription;
|
||||
entity.HeroTitle = request.HeroTitle;
|
||||
entity.HeroSubtitle = request.HeroSubtitle;
|
||||
entity.IsActive = request.IsActive;
|
||||
entity.SettingsJson = request.SettingsJson;
|
||||
|
||||
// آپلود تصویر هیرو
|
||||
if (request.ImageFileBytes is { Length: > 0 })
|
||||
{
|
||||
var result = await _fileManager.UploadImageAsync(
|
||||
"Images/SitePageSettings",
|
||||
request.ImageFileBytes,
|
||||
request.ImageFileMime ?? "image/jpeg",
|
||||
request.ImageFileName,
|
||||
cancellationToken);
|
||||
|
||||
entity.HeroImagePath = result.Main.Path;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return new SavePageSettingsResult { Id = entity.Id, Success = true };
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Commands.SavePageSettings;
|
||||
|
||||
public class SavePageSettingsCommandValidator : AbstractValidator<SavePageSettingsCommand>
|
||||
{
|
||||
private static readonly string[] ValidPageKeys = { "landing", "about", "contact", "licenses" };
|
||||
|
||||
public SavePageSettingsCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.PageKey)
|
||||
.NotEmpty().WithMessage("کلید صفحه الزامی است")
|
||||
.MaximumLength(50)
|
||||
.Must(key => ValidPageKeys.Contains(key))
|
||||
.WithMessage("کلید صفحه باید یکی از مقادیر landing, about, contact, licenses باشد");
|
||||
|
||||
RuleFor(x => x.Title)
|
||||
.NotEmpty().WithMessage("عنوان صفحه الزامی است")
|
||||
.MaximumLength(200).WithMessage("عنوان صفحه حداکثر ۲۰۰ کاراکتر");
|
||||
|
||||
RuleFor(x => x.MetaDescription)
|
||||
.MaximumLength(300).WithMessage("توضیحات متا حداکثر ۳۰۰ کاراکتر");
|
||||
|
||||
RuleFor(x => x.HeroTitle)
|
||||
.MaximumLength(200).WithMessage("عنوان هیرو حداکثر ۲۰۰ کاراکتر");
|
||||
|
||||
RuleFor(x => x.HeroSubtitle)
|
||||
.MaximumLength(500).WithMessage("زیرعنوان هیرو حداکثر ۵۰۰ کاراکتر");
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Queries.GetAllPageSettings;
|
||||
|
||||
// ── Query ──
|
||||
public class GetAllPageSettingsQuery : IRequest<List<PageSettingsSummaryDto>>
|
||||
{
|
||||
}
|
||||
|
||||
// ── Handler ──
|
||||
public class GetAllPageSettingsQueryHandler : IRequestHandler<GetAllPageSettingsQuery, List<PageSettingsSummaryDto>>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllPageSettingsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<PageSettingsSummaryDto>> Handle(GetAllPageSettingsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.SitePageSettingsEntities
|
||||
.Where(x => !x.IsDeleted)
|
||||
.OrderBy(x => x.Id)
|
||||
.Select(x => new PageSettingsSummaryDto
|
||||
{
|
||||
Id = x.Id,
|
||||
PageKey = x.PageKey,
|
||||
Title = x.Title,
|
||||
IsActive = x.IsActive,
|
||||
ImageCount = x.Images.Count(i => !i.IsDeleted),
|
||||
LastModified = x.LastModified ?? x.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
// ── DTO ──
|
||||
public class PageSettingsSummaryDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string PageKey { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; }
|
||||
public int ImageCount { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.Content;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.SitePageSettingsCQ.Queries.GetPageSettings;
|
||||
|
||||
// ── Query ──
|
||||
public class GetPageSettingsQuery : IRequest<PageSettingsDto?>
|
||||
{
|
||||
public string PageKey { get; set; } = default!;
|
||||
}
|
||||
|
||||
// ── Handler ──
|
||||
public class GetPageSettingsQueryHandler : IRequestHandler<GetPageSettingsQuery, PageSettingsDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetPageSettingsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<PageSettingsDto?> Handle(GetPageSettingsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.SitePageSettingsEntities
|
||||
.Include(x => x.Images.Where(i => !i.IsDeleted && i.IsActive).OrderBy(i => i.SortOrder))
|
||||
.FirstOrDefaultAsync(x => x.PageKey == request.PageKey && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
return MapToDto(entity);
|
||||
}
|
||||
|
||||
public static PageSettingsDto MapToDto(SitePageSettings entity) => new()
|
||||
{
|
||||
Id = entity.Id,
|
||||
PageKey = entity.PageKey,
|
||||
Title = entity.Title,
|
||||
MetaDescription = entity.MetaDescription,
|
||||
HeroTitle = entity.HeroTitle,
|
||||
HeroSubtitle = entity.HeroSubtitle,
|
||||
HeroImagePath = entity.HeroImagePath,
|
||||
IsActive = entity.IsActive,
|
||||
SettingsJson = entity.SettingsJson,
|
||||
Images = entity.Images.Select(i => new PageImageDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ImageGroup = i.ImageGroup,
|
||||
Title = i.Title,
|
||||
Subtitle = i.Subtitle,
|
||||
Description = i.Description,
|
||||
ImagePath = i.ImagePath,
|
||||
ThumbnailPath = i.ThumbnailPath,
|
||||
LinkUrl = i.LinkUrl,
|
||||
IconName = i.IconName,
|
||||
SortOrder = i.SortOrder,
|
||||
IsActive = i.IsActive
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
// ── DTOs ──
|
||||
public class PageSettingsDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string PageKey { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? HeroTitle { get; set; }
|
||||
public string? HeroSubtitle { get; set; }
|
||||
public string? HeroImagePath { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string? SettingsJson { get; set; }
|
||||
public List<PageImageDto> Images { get; set; } = new();
|
||||
}
|
||||
|
||||
public class PageImageDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ImageGroup { get; set; } = string.Empty;
|
||||
public string? Title { get; set; }
|
||||
public string? Subtitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
public string? ThumbnailPath { get; set; }
|
||||
public string? LinkUrl { get; set; }
|
||||
public string? IconName { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user