# 📄 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 Sections { get; set; } = new List(); } ``` #### 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 { public void Configure(EntityTypeBuilder 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 { public void Configure(EntityTypeBuilder 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 ContentPages => Set(); public DbSet ContentSections => Set(); ``` #### 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; ``` **Handler**: ```csharp public class GetContentPageBySlugQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; public async Task 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 ``` #### 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 _logger; public ContentGrpcService(IMediator mediator, ILogger logger) { _mediator = mediator; _logger = logger; } public override async Task GetContentPageBySlug( GetContentPageBySlugRequest request, ServerCallContext context) { var query = new GetContentPageBySlugQuery(request.Slug); var result = await _mediator.Send(query); return result.Adapt(); } public override async Task GetAllContentPages( GetAllContentPagesRequest request, ServerCallContext context) { var query = new GetAllContentPagesQuery(request.IncludeInactive); var result = await _mediator.Send(query); return result.Adapt(); } // ... سایر متدها } ``` #### 7.2 Register Service در `Program.cs` ```csharp // در WebApi/Program.cs اضافه شود: app.MapGrpcService(); ``` **فایل‌های ایجاد شده**: - ✅ `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": "

شرکت فورست از سال 1395...

", "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 درباره ما - شرکت فورست ``` ### 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 https://foursat.com/about-us 2025-12-06 monthly 0.8 https://foursat.com/contact-us 2025-12-06 monthly 0.7 ``` --- ## 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 { 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( () => 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("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 **وضعیت:** ✅ آماده برای پیاده‌سازی **اولویت:** متوسط (بعد از فیچرهای اصلی)