1434 lines
44 KiB
Markdown
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
|
|
**وضعیت:** ✅ آماده برای پیادهسازی
|
|
**اولویت:** متوسط (بعد از فیچرهای اصلی)
|
|
|