إدارة المحتوى
يتضمن FORGE نظام إدارة محتوى مدمج لإنشاء وإدارة الصفحات الثابتة مثل صفحة "من نحن" وشروط الخدمة وسياسة الخصوصية وأي صفحات محتوى مخصصة يتطلبها تطبيقك.
نظرة عامة
يوفر نظام إدارة المحتوى سير عمل كامل لإنشاء المحتوى، بما في ذلك تحرير النص الغني وبيانات SEO الوصفية والترجمات متعددة اللغات والصور المميزة والنشر المجدول.
مخطط قاعدة البيانات
يخزن جدول contents جميع الصفحات المُدارة:
| العمود | النوع | الوصف |
|---|---|---|
id | UUID | المفتاح الأساسي |
title | VARCHAR(255) | عنوان الصفحة |
slug | VARCHAR(255) | معرّف صديق للروابط (فريد) |
summary | TEXT | وصف قصير أو مقتطف |
details | TEXT | محتوى الصفحة الكامل (نص غني / HTML) |
button_text | VARCHAR(100) | نص زر الدعوة للإجراء (اختياري) |
seo | JSONB | كائن بيانات SEO الوصفية |
translations | JSONB | الترجمات حسب اللغة |
is_active | BOOLEAN | ما إذا كانت الصفحة منشورة |
publish_at | TIMESTAMP | تاريخ النشر المجدول |
expire_at | TIMESTAMP | تاريخ انتهاء الصلاحية المجدول |
sort_order | INTEGER | ترتيب العرض |
created_at | TIMESTAMP | طابع زمني لإنشاء السجل |
updated_at | TIMESTAMP | طابع زمني لآخر تحديث |
بنية كائن SEO
يخزن عمود seo من نوع JSONB بيانات تحسين محركات البحث الوصفية:
{
"title": "عنوان SEO مخصص",
"description": "وصف meta لمحركات البحث",
"keywords": "forge, cms, content",
"robots": "index, follow",
"canonical": "https://example.com/about"
}بنية كائن الترجمات
يحتوي عمود translations من نوع JSONB على المحتوى لكل لغة مدعومة:
{
"en": {
"title": "About Us",
"summary": "Learn about our company",
"details": "<p>Full content in English...</p>"
},
"ar": {
"title": "من نحن",
"summary": "تعرف على شركتنا",
"details": "<p>المحتوى الكامل بالعربية...</p>"
}
}الصور المميزة
تدعم صفحات المحتوى الصور المميزة من خلال نظام الوسائط متعدد الأشكال. يتم ربط الصور عبر جدول media باستخدام model_type = 'content' و model_id للإشارة إلى سجل المحتوى.
// Attach a featured image to a content page
media_service.attach(file, "content", content.id, "featured").await?;
// Retrieve the featured image
let image = media_service.get_first_for_model("content", content.id, "featured").await?;نقاط نهاية API
النقاط العامة
| الطريقة | المسار | الوصف |
|---|---|---|
GET | /api/contents | عرض جميع صفحات المحتوى النشطة |
GET | /api/contents/:slug | استرجاع صفحة واحدة بواسطة slug |
نقاط الإدارة
| الطريقة | المسار | الوصف |
|---|---|---|
POST | /api/admin/contents | إنشاء صفحة محتوى جديدة |
PUT | /api/admin/contents/:id | تحديث صفحة موجودة |
DELETE | /api/admin/contents/:id | حذف صفحة محتوى |
نصيحة
تُصفي النقاط العامة تلقائياً حسب is_active = true وتحترم جدولة publish_at / expire_at. يتم إرجاع الصفحات الصالحة حالياً فقط.
مثال على الطلب
# Create a new content page
curl -X POST /api/admin/contents \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"title": "About Us",
"slug": "about",
"summary": "Learn about our company",
"details": "<h2>Our Story</h2><p>Founded in 2024...</p>",
"button_text": "Contact Us",
"seo": {
"title": "About Us | My App",
"description": "Learn about our company and mission",
"robots": "index, follow"
},
"translations": {
"ar": {
"title": "من نحن",
"summary": "تعرف على شركتنا",
"details": "<h2>قصتنا</h2><p>تأسست في 2024...</p>"
}
},
"is_active": true,
"sort_order": 1
}'واجهة الإدارة
توفر لوحة الإدارة واجهة CRUD كاملة لإدارة المحتوى:
- عرض القائمة -- جدول بجميع صفحات المحتوى مع الحالة والعنوان و slug والإجراءات
- نموذج الإنشاء / التحرير -- محرر نص غني لحقل
details، حقول إدخال للعنوان و slug، حقول بيانات SEO الوصفية، وتبويبات الترجمة لكل لغة نشطة - رفع الصور -- سحب وإفلات للصورة المميزة مع معاينة
- الجدولة -- منتقيات تاريخ لـ
publish_atوexpire_at
تحذير
يجب أن يكون حقل slug فريداً عبر جميع صفحات المحتوى. تغيير slug بعد النشر سيكسر الروابط الموجودة ما لم تُنفّذ إعادة التوجيه على مستوى التطبيق.
الواجهة الأمامية العامة
يتم عرض صفحات المحتوى عبر مسارات [slug] الديناميكية على الموقع العام. عندما يتصفح الزائر /about، تجلب الواجهة الأمامية سجل المحتوى بـ slug = "about" وتعرضه.
// Next.js dynamic route: app/[slug]/page.tsx
export default async function ContentPage({ params }: { params: { slug: string } }) {
const content = await fetch(`${API_URL}/api/contents/${params.slug}`);
const data = await content.json();
return (
<article>
<h1>{data.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.details }} />
</article>
);
}نصيحة
تختار الواجهة الأمامية تلقائياً الترجمة الصحيحة بناءً على اللغة النشطة للمستخدم. إذا لم تتوفر ترجمة، يتم عرض محتوى اللغة الافتراضية.