Files
docs/archive/05-TASKS/CONTENT-PAGES-IMPLEMENTATION-PLAN.md
T
masoodafar-web 5965b98728 update
2026-01-03 18:27:49 +03:30

44 KiB

📄 Dynamic Content Pages System - Implementation Plan

نگاه کلی

یک سیستم مدیریت محتوای پویا (Dynamic CMS) برای مدیریت صفحات استاتیک مانند:

  • درباره ما (About Us)
  • تماس با ما (Contact Us)
  • قوانین و مقررات (Terms & Conditions)
  • حریم خصوصی (Privacy Policy)
  • سوالات متداول (FAQ)
  • و هر صفحه دلخواه دیگر

ویژگی‌های کلیدی

  • Flexible Structure: هر صفحه می‌تواند چندین بخش (Section) داشته باشد
  • Multi-Section Types: Hero, TextBlock, ImageGallery, ContactForm, TeamMembers, FAQ, etc.
  • JSON-Based Content: محتوای هر Section به صورت JSON ذخیره می‌شود
  • Reorderable: امکان تغییر ترتیب Sections با Drag & Drop
  • SEO-Friendly: Meta Tags, Structured Data, Sitemap
  • Multi-Language Ready: آماده برای چند زبانه (فارسی/انگلیسی)
  • BackOffice Management: مدیریت کامل از پنل ادمین

معماری سیستم

┌─────────────────────────────────────────────────────────────┐
│                     ContentPage Table                        │
├─────────────────────────────────────────────────────────────┤
│ Id, Slug (about-us), Title, MetaDescription,                │
│ IsActive, IsPublic, SortOrder, Created, CreatedBy           │
└────────────────────┬────────────────────────────────────────┘
                     │ 1-to-Many
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                   ContentSection Table                       │
├─────────────────────────────────────────────────────────────┤
│ Id, PageId (FK), SectionType (Enum), Title,                 │
│ Content (JSON), SortOrder, IsActive, Created                │
└─────────────────────────────────────────────────────────────┘

SectionType Enum:
- Hero
- TextBlock
- ImageGallery
- ContactForm
- TeamMembers
- FAQ
- Map
- SocialMedia

مراحل پیاده‌سازی (7 مرحله)

Phase 1: Domain Layer - Entities

مسیر: CMS/src/CMSMicroservice.Domain/Entities/Content/

1.1 Create ContentPage.cs

namespace CMSMicroservice.Domain.Entities.Content;

public class ContentPage : BaseAuditableEntity
{
    public long Id { get; set; }
    
    // Core Properties
    public string Slug { get; set; } = string.Empty;         // "about-us", "contact-us"
    public string Title { get; set; } = string.Empty;        // "درباره ما"
    public string? Subtitle { get; set; }                    // اختیاری
    
    // SEO Properties
    public string? MetaDescription { get; set; }
    public string? MetaKeywords { get; set; }
    public string? OgImage { get; set; }                     // Open Graph Image
    
    // Display Properties
    public bool IsActive { get; set; } = true;
    public bool IsPublic { get; set; } = true;               // نمایش در منو/سایت
    public int SortOrder { get; set; } = 0;                  // ترتیب نمایش در منو
    
    // Navigation Property
    public virtual ICollection<ContentSection> Sections { get; set; } = new List<ContentSection>();
}

1.2 Create ContentSection.cs

namespace CMSMicroservice.Domain.Entities.Content;

public class ContentSection : BaseAuditableEntity
{
    public long Id { get; set; }
    
    // Foreign Key
    public long PageId { get; set; }
    public virtual ContentPage Page { get; set; } = null!;
    
    // Section Properties
    public SectionType SectionType { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = "{}";              // JSON Content
    
    // Display Properties
    public int SortOrder { get; set; } = 0;
    public bool IsActive { get; set; } = true;
}

1.3 Create SectionType.cs (Enum)

namespace CMSMicroservice.Domain.Entities.Content;

public enum SectionType
{
    Hero = 0,              // بنر بزرگ با عکس و متن
    TextBlock = 1,         // بلوک متنی ساده (HTML)
    ImageGallery = 2,      // گالری تصاویر
    ContactForm = 3,       // فرم تماس
    TeamMembers = 4,       // اعضای تیم
    FAQ = 5,               // سوالات متداول
    Map = 6,               // نقشه (Google Maps)
    SocialMedia = 7,       // لینک‌های شبکه‌های اجتماعی
    VideoEmbed = 8,        // ویدیو Embed
    Testimonials = 9,      // نظرات مشتریان
    Features = 10,         // ویژگی‌ها (با آیکون)
    Statistics = 11,       // آمار (عدد + توضیح)
    Timeline = 12,         // تایم‌لاین (تاریخچه)
    CallToAction = 13      // دکمه Call-to-Action
}

فایل‌های ایجاد شده:

  • ContentPage.cs
  • ContentSection.cs
  • SectionType.cs

Phase 2: Infrastructure Layer - EF Core Configurations

مسیر: CMS/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/

2.1 Create ContentPageConfiguration.cs

using CMSMicroservice.Domain.Entities.Content;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content;

public class ContentPageConfiguration : IEntityTypeConfiguration<ContentPage>
{
    public void Configure(EntityTypeBuilder<ContentPage> builder)
    {
        builder.ToTable("ContentPages", "CMS");
        
        builder.HasKey(x => x.Id);
        
        builder.Property(x => x.Slug)
            .IsRequired()
            .HasMaxLength(100);
        
        builder.Property(x => x.Title)
            .IsRequired()
            .HasMaxLength(200);
        
        builder.Property(x => x.Subtitle)
            .HasMaxLength(500);
        
        builder.Property(x => x.MetaDescription)
            .HasMaxLength(500);
        
        builder.Property(x => x.MetaKeywords)
            .HasMaxLength(300);
        
        builder.Property(x => x.OgImage)
            .HasMaxLength(500);
        
        // Unique constraint on Slug
        builder.HasIndex(x => x.Slug)
            .IsUnique();
        
        // Index for active pages
        builder.HasIndex(x => new { x.IsActive, x.IsPublic, x.SortOrder });
        
        // Relationship
        builder.HasMany(x => x.Sections)
            .WithOne(x => x.Page)
            .HasForeignKey(x => x.PageId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

2.2 Create ContentSectionConfiguration.cs

using CMSMicroservice.Domain.Entities.Content;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content;

public class ContentSectionConfiguration : IEntityTypeConfiguration<ContentSection>
{
    public void Configure(EntityTypeBuilder<ContentSection> builder)
    {
        builder.ToTable("ContentSections", "CMS");
        
        builder.HasKey(x => x.Id);
        
        builder.Property(x => x.PageId)
            .IsRequired();
        
        builder.Property(x => x.SectionType)
            .IsRequired();
        
        builder.Property(x => x.Title)
            .IsRequired()
            .HasMaxLength(200);
        
        builder.Property(x => x.Content)
            .IsRequired()
            .HasColumnType("nvarchar(max)"); // JSON Storage
        
        builder.Property(x => x.SortOrder)
            .IsRequired();
        
        // Index for querying sections by page
        builder.HasIndex(x => new { x.PageId, x.SortOrder });
        
        // Index for section type filtering
        builder.HasIndex(x => x.SectionType);
    }
}

فایل‌های ایجاد شده:

  • ContentPageConfiguration.cs
  • ContentSectionConfiguration.cs

Phase 3: Update ApplicationDbContext

مسیر: CMS/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs

3.1 Add DbSets

// در بخش DbSets اضافه شود (بعد از خط 85):

// ============= Content Management System DbSets =============
public DbSet<ContentPage> ContentPages => Set<ContentPage>();
public DbSet<ContentSection> ContentSections => Set<ContentSection>();

3.2 Configuration Auto-Discovery

Configuration ها به صورت خودکار از Assembly کشف می‌شوند (قبلاً پیاده‌سازی شده):

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    // ...
}

تغییرات:

  • 2 DbSet جدید اضافه شد

Phase 4: Database Migration

مسیر: CMS/src/CMSMicroservice.Infrastructure/

4.1 Create Migration

cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Infrastructure
dotnet ef migrations add AddDynamicContentSystem --startup-project ../CMSMicroservice.WebApi

4.2 Review Migration

بررسی فایل Migration ایجاد شده:

  • Migrations/YYYYMMDD_AddDynamicContentSystem.cs

4.3 Apply Migration

dotnet ef database update --startup-project ../CMSMicroservice.WebApi

نتیجه:

  • جدول [CMS].[ContentPages] ایجاد شد
  • جدول [CMS].[ContentSections] ایجاد شد
  • Indexes و Constraints اعمال شدند

Phase 5: Application Layer - CQRS

مسیر: CMS/src/CMSMicroservice.Application/ContentCQ/

5.1 Queries

5.1.1 GetContentPageBySlug
ContentCQ/
├── Queries/
│   └── GetContentPageBySlug/
│       ├── GetContentPageBySlugQuery.cs
│       ├── GetContentPageBySlugQueryHandler.cs
│       ├── GetContentPageBySlugQueryValidator.cs
│       └── GetContentPageBySlugResponseDto.cs

Query:

public record GetContentPageBySlugQuery(string Slug) : IRequest<GetContentPageBySlugResponseDto>;

Handler:

public class GetContentPageBySlugQueryHandler : IRequestHandler<GetContentPageBySlugQuery, GetContentPageBySlugResponseDto>
{
    private readonly IApplicationDbContext _context;

    public async Task<GetContentPageBySlugResponseDto> Handle(...)
    {
        var page = await _context.ContentPages
            .Include(x => x.Sections.Where(s => s.IsActive))
            .FirstOrDefaultAsync(x => x.Slug == request.Slug && x.IsActive);
        
        // Map to DTO
    }
}
5.1.2 GetAllContentPages (for BackOffice)
ContentCQ/
├── Queries/
│   └── GetAllContentPages/
│       ├── GetAllContentPagesQuery.cs
│       ├── GetAllContentPagesQueryHandler.cs
│       └── GetAllContentPagesResponseDto.cs

5.2 Commands

5.2.1 CreateContentPage
ContentCQ/
├── Commands/
│   └── CreateContentPage/
│       ├── CreateContentPageCommand.cs
│       ├── CreateContentPageCommandHandler.cs
│       ├── CreateContentPageCommandValidator.cs
│       └── CreateContentPageResponseDto.cs
5.2.2 UpdateContentPage
ContentCQ/
├── Commands/
│   └── UpdateContentPage/
│       ├── UpdateContentPageCommand.cs
│       ├── UpdateContentPageCommandHandler.cs
│       ├── UpdateContentPageCommandValidator.cs
│       └── UpdateContentPageResponseDto.cs
5.2.3 DeleteContentPage
ContentCQ/
├── Commands/
│   └── DeleteContentPage/
│       ├── DeleteContentPageCommand.cs
│       └── DeleteContentPageCommandHandler.cs
5.2.4 AddContentSection
ContentCQ/
├── Commands/
│   └── AddContentSection/
│       ├── AddContentSectionCommand.cs
│       ├── AddContentSectionCommandHandler.cs
│       ├── AddContentSectionCommandValidator.cs
│       └── AddContentSectionResponseDto.cs
5.2.5 UpdateContentSection
ContentCQ/
├── Commands/
│   └── UpdateContentSection/
│       ├── UpdateContentSectionCommand.cs
│       ├── UpdateContentSectionCommandHandler.cs
│       ├── UpdateContentSectionCommandValidator.cs
│       └── UpdateContentSectionResponseDto.cs
5.2.6 DeleteContentSection
ContentCQ/
├── Commands/
│   └── DeleteContentSection/
│       ├── DeleteContentSectionCommand.cs
│       └── DeleteContentSectionCommandHandler.cs
5.2.7 ReorderContentSections
ContentCQ/
├── Commands/
│   └── ReorderContentSections/
│       ├── ReorderContentSectionsCommand.cs
│       └── ReorderContentSectionsCommandHandler.cs

کل فایل‌های Application Layer: ~21 فایل


Phase 6: API Layer - Protobuf & gRPC

مسیر: CMS/src/CMSMicroservice.Protobuf/Protos/

6.1 Create content.proto

syntax = "proto3";

option csharp_namespace = "CMSMicroservice.Protobuf.Protos.Content";

package content;

import "google/protobuf/timestamp.proto";

// ============= Services =============

service ContentService {
  rpc GetContentPageBySlug(GetContentPageBySlugRequest) returns (GetContentPageBySlugResponse);
  rpc GetAllContentPages(GetAllContentPagesRequest) returns (GetAllContentPagesResponse);
  rpc CreateContentPage(CreateContentPageRequest) returns (CreateContentPageResponse);
  rpc UpdateContentPage(UpdateContentPageRequest) returns (UpdateContentPageResponse);
  rpc DeleteContentPage(DeleteContentPageRequest) returns (DeleteContentPageResponse);
  rpc AddContentSection(AddContentSectionRequest) returns (AddContentSectionResponse);
  rpc UpdateContentSection(UpdateContentSectionRequest) returns (UpdateContentSectionResponse);
  rpc DeleteContentSection(DeleteContentSectionRequest) returns (DeleteContentSectionResponse);
  rpc ReorderContentSections(ReorderContentSectionsRequest) returns (ReorderContentSectionsResponse);
}

// ============= Messages =============

// GetContentPageBySlug
message GetContentPageBySlugRequest {
  string slug = 1;
}

message GetContentPageBySlugResponse {
  int64 id = 1;
  string slug = 2;
  string title = 3;
  string subtitle = 4;
  string meta_description = 5;
  string meta_keywords = 6;
  string og_image = 7;
  repeated ContentSectionModel sections = 8;
}

message ContentSectionModel {
  int64 id = 1;
  string section_type = 2;  // "Hero", "TextBlock", etc.
  string title = 3;
  string content = 4;       // JSON string
  int32 sort_order = 5;
  bool is_active = 6;
}

// GetAllContentPages
message GetAllContentPagesRequest {
  bool include_inactive = 1;
}

message GetAllContentPagesResponse {
  repeated ContentPageSummary pages = 1;
}

message ContentPageSummary {
  int64 id = 1;
  string slug = 2;
  string title = 3;
  bool is_active = 4;
  bool is_public = 5;
  int32 sections_count = 6;
  google.protobuf.Timestamp created = 7;
}

// CreateContentPage
message CreateContentPageRequest {
  string slug = 1;
  string title = 2;
  string subtitle = 3;
  string meta_description = 4;
  string meta_keywords = 5;
  string og_image = 6;
  bool is_active = 7;
  bool is_public = 8;
  int32 sort_order = 9;
}

message CreateContentPageResponse {
  bool success = 1;
  string message = 2;
  int64 page_id = 3;
}

// UpdateContentPage
message UpdateContentPageRequest {
  int64 page_id = 1;
  string slug = 2;
  string title = 3;
  string subtitle = 4;
  string meta_description = 5;
  string meta_keywords = 6;
  string og_image = 7;
  bool is_active = 8;
  bool is_public = 9;
  int32 sort_order = 10;
}

message UpdateContentPageResponse {
  bool success = 1;
  string message = 2;
}

// DeleteContentPage
message DeleteContentPageRequest {
  int64 page_id = 1;
}

message DeleteContentPageResponse {
  bool success = 1;
  string message = 2;
}

// AddContentSection
message AddContentSectionRequest {
  int64 page_id = 1;
  string section_type = 2;
  string title = 3;
  string content = 4;      // JSON string
  int32 sort_order = 5;
  bool is_active = 6;
}

message AddContentSectionResponse {
  bool success = 1;
  string message = 2;
  int64 section_id = 3;
}

// UpdateContentSection
message UpdateContentSectionRequest {
  int64 section_id = 1;
  string section_type = 2;
  string title = 3;
  string content = 4;
  int32 sort_order = 5;
  bool is_active = 6;
}

message UpdateContentSectionResponse {
  bool success = 1;
  string message = 2;
}

// DeleteContentSection
message DeleteContentSectionRequest {
  int64 section_id = 1;
}

message DeleteContentSectionResponse {
  bool success = 1;
  string message = 2;
}

// ReorderContentSections
message ReorderContentSectionsRequest {
  int64 page_id = 1;
  repeated SectionOrder orders = 2;
}

message SectionOrder {
  int64 section_id = 1;
  int32 new_sort_order = 2;
}

message ReorderContentSectionsResponse {
  bool success = 1;
  string message = 2;
}

6.2 Update .csproj

<!-- در CMSMicroservice.Protobuf.csproj -->
<ItemGroup>
  <Protobuf Include="Protos\content.proto" GrpcServices="Server" />
</ItemGroup>

6.3 Generate Protobuf Code

cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Protobuf
dotnet build

فایل‌های ایجاد شده:

  • content.proto
  • Auto-generated C# classes

Phase 7: WebApi Layer - gRPC Service

مسیر: CMS/src/CMSMicroservice.WebApi/Services/

7.1 Create ContentGrpcService.cs

using CMSMicroservice.Application.ContentCQ.Commands.*;
using CMSMicroservice.Application.ContentCQ.Queries.*;
using CMSMicroservice.Protobuf.Protos.Content;
using Grpc.Core;
using Mapster;
using MediatR;

namespace CMSMicroservice.WebApi.Services;

public class ContentGrpcService : ContentService.ContentServiceBase
{
    private readonly IMediator _mediator;
    private readonly ILogger<ContentGrpcService> _logger;

    public ContentGrpcService(IMediator mediator, ILogger<ContentGrpcService> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }

    public override async Task<GetContentPageBySlugResponse> GetContentPageBySlug(
        GetContentPageBySlugRequest request, 
        ServerCallContext context)
    {
        var query = new GetContentPageBySlugQuery(request.Slug);
        var result = await _mediator.Send(query);
        
        return result.Adapt<GetContentPageBySlugResponse>();
    }

    public override async Task<GetAllContentPagesResponse> GetAllContentPages(
        GetAllContentPagesRequest request, 
        ServerCallContext context)
    {
        var query = new GetAllContentPagesQuery(request.IncludeInactive);
        var result = await _mediator.Send(query);
        
        return result.Adapt<GetAllContentPagesResponse>();
    }

    // ... سایر متدها
}

7.2 Register Service در Program.cs

// در WebApi/Program.cs اضافه شود:
app.MapGrpcService<ContentGrpcService>();

فایل‌های ایجاد شده:

  • ContentGrpcService.cs
  • تغییرات در Program.cs

JSON Schema Examples

1. Hero Section

{
  "backgroundImage": "/images/hero-bg.jpg",
  "title": "به شرکت فورست خوش آمدید",
  "subtitle": "راهکارهای نوین دیجیتال مارکتینگ",
  "buttonText": "شروع کنید",
  "buttonLink": "/contact",
  "alignment": "center",
  "overlayOpacity": 0.5
}

2. TextBlock Section

{
  "title": "درباره شرکت",
  "body": "<p>شرکت فورست از سال 1395...</p>",
  "backgroundColor": "#f5f5f5",
  "textAlign": "justify",
  "padding": "40px 20px",
  "hasReadMore": false
}

3. ContactForm Section

{
  "title": "تماس با ما",
  "description": "لطفاً فرم زیر را پر کنید",
  "fields": [
    {
      "name": "fullName",
      "label": "نام و نام خانوادگی",
      "type": "text",
      "required": true,
      "placeholder": "نام خود را وارد کنید"
    },
    {
      "name": "email",
      "label": "ایمیل",
      "type": "email",
      "required": true,
      "placeholder": "example@email.com"
    },
    {
      "name": "phone",
      "label": "شماره تماس",
      "type": "tel",
      "required": false,
      "placeholder": "09123456789"
    },
    {
      "name": "message",
      "label": "پیام",
      "type": "textarea",
      "required": true,
      "placeholder": "پیام خود را بنویسید...",
      "rows": 5
    }
  ],
  "submitButton": "ارسال پیام",
  "successMessage": "پیام شما با موفقیت ارسال شد",
  "errorMessage": "خطا در ارسال پیام",
  "emailTo": "info@foursat.com"
}

4. TeamMembers Section

{
  "title": "تیم ما",
  "members": [
    {
      "name": "علی احمدی",
      "position": "مدیر عامل",
      "image": "/images/team/ali.jpg",
      "bio": "بیش از 15 سال تجربه در...",
      "linkedin": "https://linkedin.com/in/ali",
      "email": "ali@foursat.com"
    },
    {
      "name": "سارا محمدی",
      "position": "مدیر فنی",
      "image": "/images/team/sara.jpg",
      "bio": "متخصص در زمینه...",
      "linkedin": "https://linkedin.com/in/sara",
      "email": "sara@foursat.com"
    }
  ],
  "layout": "grid",
  "columns": 3
}

5. FAQ Section

{
  "title": "سوالات متداول",
  "questions": [
    {
      "question": "چگونه می‌توانم ثبت نام کنم؟",
      "answer": "برای ثبت نام کافیست به صفحه...",
      "isExpanded": false
    },
    {
      "question": "روش‌های پرداخت چیست؟",
      "answer": "شما می‌توانید از طریق...",
      "isExpanded": false
    }
  ],
  "layout": "accordion"
}

6. Map Section

{
  "title": "موقعیت ما",
  "latitude": 35.6892,
  "longitude": 51.3890,
  "zoom": 15,
  "markerTitle": "شرکت فورست",
  "address": "تهران، خیابان ولیعصر، پلاک 123",
  "height": "400px",
  "showControls": true
}

7. SocialMedia Section

{
  "title": "ما را دنبال کنید",
  "platforms": [
    {
      "name": "Instagram",
      "icon": "instagram",
      "url": "https://instagram.com/foursat",
      "color": "#E4405F"
    },
    {
      "name": "Telegram",
      "icon": "telegram",
      "url": "https://t.me/foursat",
      "color": "#0088cc"
    },
    {
      "name": "LinkedIn",
      "icon": "linkedin",
      "url": "https://linkedin.com/company/foursat",
      "color": "#0077b5"
    }
  ],
  "layout": "horizontal",
  "iconSize": "large"
}

8. Features Section

{
  "title": "ویژگی‌های ما",
  "features": [
    {
      "icon": "shield-check",
      "title": "امنیت بالا",
      "description": "تمام اطلاعات شما با استانداردهای..."
    },
    {
      "icon": "clock",
      "title": "پشتیبانی 24/7",
      "description": "تیم پشتیبانی ما همیشه آماده..."
    },
    {
      "icon": "globe",
      "title": "دسترسی جهانی",
      "description": "از هر جای دنیا می‌توانید..."
    }
  ],
  "layout": "grid",
  "columns": 3
}

9. Statistics Section

{
  "title": "اعداد و ارقام",
  "stats": [
    {
      "number": 10000,
      "suffix": "+",
      "label": "کاربر فعال",
      "icon": "users",
      "animateOnView": true
    },
    {
      "number": 500,
      "suffix": "+",
      "label": "پروژه موفق",
      "icon": "briefcase",
      "animateOnView": true
    },
    {
      "number": 98,
      "suffix": "%",
      "label": "رضایت مشتریان",
      "icon": "heart",
      "animateOnView": true
    }
  ],
  "layout": "horizontal",
  "backgroundColor": "#f8f9fa"
}

10. Timeline Section

{
  "title": "تاریخچه شرکت",
  "events": [
    {
      "year": "1395",
      "title": "تأسیس شرکت",
      "description": "شرکت فورست با هدف...",
      "image": "/images/timeline/2016.jpg"
    },
    {
      "year": "1397",
      "title": "توسعه محصول اول",
      "description": "راه‌اندازی اولین محصول...",
      "image": "/images/timeline/2018.jpg"
    },
    {
      "year": "1400",
      "title": "جایزه بهترین استارتاپ",
      "description": "دریافت جایزه ملی...",
      "image": "/images/timeline/2021.jpg"
    }
  ],
  "layout": "vertical",
  "alternateLayout": true
}

BackOffice UI Design

صفحه لیست (ContentPagesMainPage.razor)

┌─────────────────────────────────────────────────────────────┐
│ 📄 مدیریت صفحات محتوا                    [+ صفحه جدید]     │
├─────────────────────────────────────────────────────────────┤
│ 🔍 جستجو: [_____________]  🔧 فیلتر: [همه ▼]              │
├─────┬──────────────┬────────┬────────┬──────────┬──────────┤
│ ID  │ عنوان صفحه   │ Slug   │ وضعیت  │ Sections │ عملیات   │
├─────┼──────────────┼────────┼────────┼──────────┼──────────┤
│ 1   │ درباره ما    │about-us│ ✅ فعال│    5     │[✏️][🗑️][👁️]│
│ 2   │ تماس با ما   │contact │ ✅ فعال│    3     │[✏️][🗑️][👁️]│
│ 3   │ قوانین      │terms   │ ❌ غیرف│    2     │[✏️][🗑️][👁️]│
└─────┴──────────────┴────────┴────────┴──────────┴──────────┘

صفحه ویرایش (ContentPageEditor.razor)

┌─────────────────────────────────────────────────────────────┐
│ ✏️ ویرایش صفحه: درباره ما                 [💾 ذخیره] [❌]  │
├─────────────────────────────────────────────────────────────┤
│ Tab: [📝 اطلاعات پایه] [🧩 بخش‌ها] [🔍 SEO]               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ عنوان: [______________________________________]             │
│ Slug:  [about-us________________] (منحصر به فرد)           │
│ زیرعنوان: [________________________________]              │
│                                                             │
│ ☑ فعال   ☑ نمایش عمومی   ترتیب: [0___]                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Tab بخش‌ها (Sections)

┌─────────────────────────────────────────────────────────────┐
│ 🧩 بخش‌های صفحه                         [+ افزودن بخش]     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ ┌─────────────────────────────────────┐                     │
│ │ ☰ 1. Hero Section       [↑][↓][✏️][🗑️]│                   │
│ │ نوع: Hero | فعال: ✅                │                     │
│ │ محتوا: بنر خوش آمد گویی...          │                     │
│ └─────────────────────────────────────┘                     │
│                                                             │
│ ┌─────────────────────────────────────┐                     │
│ │ ☰ 2. TextBlock          [↑][↓][✏️][🗑️]│                   │
│ │ نوع: TextBlock | فعال: ✅           │                     │
│ │ محتوا: درباره شرکت...               │                     │
│ └─────────────────────────────────────┘                     │
│                                                             │
│ ┌─────────────────────────────────────┐                     │
│ │ ☰ 3. TeamMembers        [↑][↓][✏️][🗑️]│                   │
│ │ نوع: TeamMembers | فعال: ✅         │                     │
│ │ محتوا: 4 عضو تیم                    │                     │
│ └─────────────────────────────────────┘                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

ویرایشگر Section (SectionEditor.razor)

┌─────────────────────────────────────────────────────────────┐
│ ✏️ ویرایش بخش: Hero Section              [💾 ذخیره] [❌]  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│ نوع بخش: [Hero ▼]                                          │
│ عنوان: [بنر خوش آمد گویی_________________]                │
│ فعال: ☑                                                    │
│                                                             │
│ ─── تنظیمات Hero Section ───                               │
│                                                             │
│ تصویر پس‌زمینه: [انتخاب فایل] /images/hero-bg.jpg         │
│ عنوان اصلی: [به شرکت فورست خوش آمدید____]                │
│ زیرعنوان: [راهکارهای نوین دیجیتال مارکتینگ__]            │
│ متن دکمه: [شروع کنید_________]                            │
│ لینک دکمه: [/contact__________]                           │
│ موقعیت متن: [○ چپ  ●  وسط  ○ راست]                       │
│ شفافیت لایه: [▓▓▓▓▓▓░░░░] 50%                             │
│                                                             │
│ 👁️ [پیش‌نمایش]                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Validation Rules

ContentPage Validation

  • Slug:

    • الزامی
    • فقط حروف کوچک، اعداد، و خط تیره (-)
    • طول: 3-100 کاراکتر
    • منحصر به فرد
    • مثال: about-us, contact-us, terms-conditions
  • Title:

    • الزامی
    • طول: 3-200 کاراکتر
  • MetaDescription:

    • اختیاری
    • طول: حداکثر 500 کاراکتر

ContentSection Validation

  • SectionType:

    • الزامی
    • باید یکی از مقادیر Enum باشد
  • Title:

    • الزامی
    • طول: 3-200 کاراکتر
  • Content:

    • الزامی
    • باید JSON معتبر باشد
    • Schema validation بر اساس SectionType

SEO Optimization

1. Meta Tags

<!-- در صفحه About Us -->
<title>درباره ما - شرکت فورست</title>
<meta name="description" content="شرکت فورست از سال 1395 فعالیت خود را...">
<meta name="keywords" content="فورست، درباره ما، تاریخچه شرکت">

<!-- Open Graph -->
<meta property="og:title" content="درباره ما - شرکت فورست">
<meta property="og:description" content="شرکت فورست از سال 1395...">
<meta property="og:image" content="https://foursat.com/og-about.jpg">
<meta property="og:type" content="website">

2. Structured Data (JSON-LD)

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "شرکت فورست",
  "url": "https://foursat.com",
  "logo": "https://foursat.com/logo.png",
  "contactPoint": {
    "@type": "ContactPoint",
    "telephone": "+98-21-12345678",
    "contactType": "Customer Service",
    "areaServed": "IR",
    "availableLanguage": ["fa", "en"]
  },
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "خیابان ولیعصر، پلاک 123",
    "addressLocality": "تهران",
    "postalCode": "1234567890",
    "addressCountry": "IR"
  }
}

3. Sitemap.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://foursat.com/about-us</loc>
    <lastmod>2025-12-06</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>
  <url>
    <loc>https://foursat.com/contact-us</loc>
    <lastmod>2025-12-06</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.7</priority>
  </url>
</urlset>

Multi-Language Support (آینده)

Approach 1: Separate Tables

ContentPage (fa) ──┐
ContentPage (en) ──┼─→ ContentPageTranslation
ContentPage (ar) ──┘

Approach 2: JSON Field

public class ContentPage
{
    public string Title { get; set; }              // فارسی (پیشفرض)
    public string TitleTranslations { get; set; }  // JSON: {"en": "About Us", "ar": "معلومات عنا"}
}

Approach 3: Separate Content Column

public class ContentSection
{
    public string Content { get; set; }      // فارسی
    public string ContentEn { get; set; }    // انگلیسی
    public string ContentAr { get; set; }    // عربی
}

توصیه: Approach 2 (JSON Field) - flexible و مقیاس‌پذیر


Performance Optimization

1. Caching Strategy

// Cache صفحات برای 30 دقیقه
services.AddMemoryCache();

public class GetContentPageBySlugQueryHandler
{
    private readonly IMemoryCache _cache;
    
    public async Task<...> Handle(...)
    {
        var cacheKey = $"content-page-{request.Slug}";
        
        if (!_cache.TryGetValue(cacheKey, out GetContentPageBySlugResponseDto result))
        {
            result = await GetFromDatabase(...);
            
            _cache.Set(cacheKey, result, TimeSpan.FromMinutes(30));
        }
        
        return result;
    }
}

2. Database Indexes

-- Index برای Slug (Unique)
CREATE UNIQUE INDEX IX_ContentPages_Slug ON ContentPages(Slug);

-- Index برای فیلتر صفحات فعال
CREATE INDEX IX_ContentPages_Active ON ContentPages(IsActive, IsPublic, SortOrder);

-- Index برای Sections
CREATE INDEX IX_ContentSections_PageId ON ContentSections(PageId, SortOrder);

3. Lazy Loading

// فقط Sections فعال را بارگذاری کن
var page = await _context.ContentPages
    .Include(x => x.Sections.Where(s => s.IsActive).OrderBy(s => s.SortOrder))
    .FirstOrDefaultAsync(x => x.Slug == slug);

Security Considerations

1. Authorization

// فقط Admin می‌تواند صفحات را مدیریت کند
[Authorize(Roles = "Admin")]
public class ContentGrpcService : ContentService.ContentServiceBase
{
    // ...
}

2. Input Validation

public class CreateContentPageCommandValidator : AbstractValidator<CreateContentPageCommand>
{
    public CreateContentPageCommandValidator()
    {
        RuleFor(x => x.Slug)
            .NotEmpty()
            .Matches("^[a-z0-9-]+$") // فقط حروف کوچک، اعداد، و خط تیره
            .Length(3, 100);
        
        RuleFor(x => x.Title)
            .NotEmpty()
            .Length(3, 200);
    }
}

3. XSS Prevention

// Sanitize HTML content در TextBlock
using HtmlAgilityPack;

public class ContentSanitizer
{
    public string SanitizeHtml(string html)
    {
        var doc = new HtmlDocument();
        doc.LoadHtml(html);
        
        // حذف تگ‌های خطرناک
        doc.DocumentNode.Descendants()
            .Where(n => n.Name == "script" || n.Name == "iframe")
            .ToList()
            .ForEach(n => n.Remove());
        
        return doc.DocumentNode.OuterHtml;
    }
}

Testing Strategy

1. Unit Tests

// Test: GetContentPageBySlug با Slug نامعتبر
[Fact]
public async Task GetContentPageBySlug_WithInvalidSlug_ReturnsNull()
{
    // Arrange
    var handler = new GetContentPageBySlugQueryHandler(_context);
    var query = new GetContentPageBySlugQuery("invalid-slug");
    
    // Act
    var result = await handler.Handle(query, CancellationToken.None);
    
    // Assert
    Assert.Null(result);
}

2. Integration Tests

// Test: ایجاد صفحه با Slug تکراری
[Fact]
public async Task CreateContentPage_WithDuplicateSlug_ThrowsException()
{
    // Arrange
    await CreatePage("about-us");
    
    // Act & Assert
    await Assert.ThrowsAsync<DbUpdateException>(
        () => CreatePage("about-us")
    );
}

3. E2E Tests

// Test: BackOffice - ایجاد و ویرایش صفحه
[Fact]
public async Task BackOffice_CreateAndEditPage_Success()
{
    // 1. Navigate to ContentPages
    await Page.GotoAsync("/content-pages");
    
    // 2. Click "New Page"
    await Page.ClickAsync("button:has-text('صفحه جدید')");
    
    // 3. Fill form
    await Page.FillAsync("input[name='title']", "تست صفحه");
    await Page.FillAsync("input[name='slug']", "test-page");
    
    // 4. Submit
    await Page.ClickAsync("button:has-text('ذخیره')");
    
    // 5. Verify
    await Expect(Page.Locator("text=تست صفحه")).ToBeVisibleAsync();
}

Deployment Checklist

Pre-Deployment

  • همه Unit Tests پاس شوند
  • Integration Tests پاس شوند
  • Migration در Staging اجرا شود
  • Performance Testing انجام شود
  • Security Audit انجام شود
  • Documentation کامل باشد

Deployment Steps

  1. Backup دیتابیس Production
  2. اجرای Migration در Production
  3. Deploy کد جدید
  4. Smoke Testing
  5. Cache را Clear کنید
  6. Monitor logs برای 24 ساعت

Post-Deployment

  • Verify صفحات About Us و Contact Us
  • بررسی Performance Metrics
  • بررسی Error Logs
  • User Acceptance Testing (UAT)

Monitoring & Analytics

1. Application Insights

// Log Page Views
_telemetry.TrackPageView(new PageViewTelemetry
{
    Name = $"ContentPage-{slug}",
    Url = new Uri($"https://foursat.com/{slug}")
});

2. Custom Metrics

// Track Section Rendering Time
using (_telemetry.StartOperation<RequestTelemetry>("RenderSection"))
{
    await RenderSection(section);
}

3. Alerts

  • ⚠️ اگر Page Load Time > 3 ثانیه
  • ⚠️ اگر Error Rate > 1%
  • ⚠️ اگر Cache Miss Rate > 20%

Future Enhancements

Phase 8: Advanced Features (آینده)

  • Versioning: نگهداری تاریخچه تغییرات
  • Scheduling: زمان‌بندی انتشار محتوا
  • A/B Testing: تست نسخه‌های مختلف
  • Analytics Dashboard: آمار بازدید صفحات
  • SEO Score: امتیاز SEO برای هر صفحه
  • Content Templates: قالب‌های آماده برای صفحات
  • Workflow: تایید چند مرحله‌ای (Draft → Review → Published)
  • Media Library: مدیریت تصاویر و فایل‌ها
  • Comments System: سیستم نظرات برای صفحات
  • Related Pages: پیشنهاد صفحات مرتبط

خلاصه آمار پیاده‌سازی

Phase فایل‌ها خطوط کد تقریبی زمان تخمینی
1. Domain Entities 3 ~150 1 ساعت
2. EF Configurations 2 ~100 1 ساعت
3. DbContext Update 1 ~10 15 دقیقه
4. Migration 1 Auto 30 دقیقه
5. Application (CQRS) ~21 ~800 4 ساعت
6. Protobuf 1 ~200 1 ساعت
7. WebApi (gRPC) 1 ~150 1 ساعت
Total ~30 ~1,410 ~9 ساعت

BackOffice UI (اختیاری)

Phase فایل‌ها خطوط کد تقریبی زمان تخمینی
8. Razor Pages ~5 ~800 4 ساعت
9. Services ~2 ~200 1 ساعت
10. Components ~10 ~500 3 ساعت
Total ~17 ~1,500 ~8 ساعت

جمع کل: ~47 فایل، ~2,910 خط کد، ~17 ساعت توسعه


نتیجه‌گیری

این سیستم یک راهکار کامل و انعطاف‌پذیر برای مدیریت صفحات محتوایی ارائه می‌دهد که:

Scalable: امکان افزودن هر تعداد صفحه و بخش
Flexible: ساختار JSON برای محتوای پویا
Maintainable: معماری Clean Architecture و CQRS
User-Friendly: رابط کاربری BackOffice ساده و کاربردی
SEO-Optimized: Meta Tags و Structured Data
Production-Ready: Caching، Validation، Security

زمان شروع پیاده‌سازی: هر زمان که آماده باشید! 🚀


نسخه: 1.0
تاریخ: December 6, 2025
وضعیت: آماده برای پیاده‌سازی
اولویت: متوسط (بعد از فیچرهای اصلی)