Files
docs/05-TASKS/CONTENT-PAGES-IMPLEMENTATION-PLAN.md
T

1434 lines
44 KiB
Markdown

# 📄 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`
```csharp
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`
```csharp
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)
```csharp
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`
```csharp
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`
```csharp
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
```csharp
// در بخش 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 کشف می‌شوند (قبلاً پیاده‌سازی شده):
```csharp
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
// ...
}
```
**تغییرات**:
- ✅ 2 DbSet جدید اضافه شد
---
### ✅ Phase 4: Database Migration
**مسیر**: `CMS/src/CMSMicroservice.Infrastructure/`
#### 4.1 Create Migration
```bash
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
```bash
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**:
```csharp
public record GetContentPageBySlugQuery(string Slug) : IRequest<GetContentPageBySlugResponseDto>;
```
**Handler**:
```csharp
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`
```protobuf
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`
```xml
<!-- در CMSMicroservice.Protobuf.csproj -->
<ItemGroup>
<Protobuf Include="Protos\content.proto" GrpcServices="Server" />
</ItemGroup>
```
#### 6.3 Generate Protobuf Code
```bash
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`
```csharp
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`
```csharp
// در WebApi/Program.cs اضافه شود:
app.MapGrpcService<ContentGrpcService>();
```
**فایل‌های ایجاد شده**:
-`ContentGrpcService.cs`
- ✅ تغییرات در `Program.cs`
---
## JSON Schema Examples
### 1. Hero Section
```json
{
"backgroundImage": "/images/hero-bg.jpg",
"title": "به شرکت فورست خوش آمدید",
"subtitle": "راهکارهای نوین دیجیتال مارکتینگ",
"buttonText": "شروع کنید",
"buttonLink": "/contact",
"alignment": "center",
"overlayOpacity": 0.5
}
```
### 2. TextBlock Section
```json
{
"title": "درباره شرکت",
"body": "<p>شرکت فورست از سال 1395...</p>",
"backgroundColor": "#f5f5f5",
"textAlign": "justify",
"padding": "40px 20px",
"hasReadMore": false
}
```
### 3. ContactForm Section
```json
{
"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
```json
{
"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
```json
{
"title": "سوالات متداول",
"questions": [
{
"question": "چگونه می‌توانم ثبت نام کنم؟",
"answer": "برای ثبت نام کافیست به صفحه...",
"isExpanded": false
},
{
"question": "روش‌های پرداخت چیست؟",
"answer": "شما می‌توانید از طریق...",
"isExpanded": false
}
],
"layout": "accordion"
}
```
### 6. Map Section
```json
{
"title": "موقعیت ما",
"latitude": 35.6892,
"longitude": 51.3890,
"zoom": 15,
"markerTitle": "شرکت فورست",
"address": "تهران، خیابان ولیعصر، پلاک 123",
"height": "400px",
"showControls": true
}
```
### 7. SocialMedia Section
```json
{
"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
```json
{
"title": "ویژگی‌های ما",
"features": [
{
"icon": "shield-check",
"title": "امنیت بالا",
"description": "تمام اطلاعات شما با استانداردهای..."
},
{
"icon": "clock",
"title": "پشتیبانی 24/7",
"description": "تیم پشتیبانی ما همیشه آماده..."
},
{
"icon": "globe",
"title": "دسترسی جهانی",
"description": "از هر جای دنیا می‌توانید..."
}
],
"layout": "grid",
"columns": 3
}
```
### 9. Statistics Section
```json
{
"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
```json
{
"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
```html
<!-- در صفحه 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)
```json
{
"@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
<?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
```csharp
public class ContentPage
{
public string Title { get; set; } // فارسی (پیشفرض)
public string TitleTranslations { get; set; } // JSON: {"en": "About Us", "ar": "معلومات عنا"}
}
```
### Approach 3: Separate Content Column
```csharp
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
```csharp
// 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
```sql
-- 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
```csharp
// فقط 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
```csharp
// فقط Admin می‌تواند صفحات را مدیریت کند
[Authorize(Roles = "Admin")]
public class ContentGrpcService : ContentService.ContentServiceBase
{
// ...
}
```
### 2. Input Validation
```csharp
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
```csharp
// 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
```csharp
// 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
```csharp
// 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
```csharp
// 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
```csharp
// Log Page Views
_telemetry.TrackPageView(new PageViewTelemetry
{
Name = $"ContentPage-{slug}",
Url = new Uri($"https://foursat.com/{slug}")
});
```
### 2. Custom Metrics
```csharp
// 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
**وضعیت:** ✅ آماده برای پیاده‌سازی
**اولویت:** متوسط (بعد از فیچرهای اصلی)