Skip to content

إدارة المحتوى

يتضمن FORGE نظام إدارة محتوى مدمج لإنشاء وإدارة الصفحات الثابتة مثل صفحة "من نحن" وشروط الخدمة وسياسة الخصوصية وأي صفحات محتوى مخصصة يتطلبها تطبيقك.

نظرة عامة

يوفر نظام إدارة المحتوى سير عمل كامل لإنشاء المحتوى، بما في ذلك تحرير النص الغني وبيانات SEO الوصفية والترجمات متعددة اللغات والصور المميزة والنشر المجدول.

مخطط قاعدة البيانات

يخزن جدول contents جميع الصفحات المُدارة:

العمودالنوعالوصف
idUUIDالمفتاح الأساسي
titleVARCHAR(255)عنوان الصفحة
slugVARCHAR(255)معرّف صديق للروابط (فريد)
summaryTEXTوصف قصير أو مقتطف
detailsTEXTمحتوى الصفحة الكامل (نص غني / HTML)
button_textVARCHAR(100)نص زر الدعوة للإجراء (اختياري)
seoJSONBكائن بيانات SEO الوصفية
translationsJSONBالترجمات حسب اللغة
is_activeBOOLEANما إذا كانت الصفحة منشورة
publish_atTIMESTAMPتاريخ النشر المجدول
expire_atTIMESTAMPتاريخ انتهاء الصلاحية المجدول
sort_orderINTEGERترتيب العرض
created_atTIMESTAMPطابع زمني لإنشاء السجل
updated_atTIMESTAMPطابع زمني لآخر تحديث

بنية كائن SEO

يخزن عمود seo من نوع JSONB بيانات تحسين محركات البحث الوصفية:

json
{
  "title": "عنوان SEO مخصص",
  "description": "وصف meta لمحركات البحث",
  "keywords": "forge, cms, content",
  "robots": "index, follow",
  "canonical": "https://example.com/about"
}

بنية كائن الترجمات

يحتوي عمود translations من نوع JSONB على المحتوى لكل لغة مدعومة:

json
{
  "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 للإشارة إلى سجل المحتوى.

rust
// 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. يتم إرجاع الصفحات الصالحة حالياً فقط.

مثال على الطلب

bash
# 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" وتعرضه.

tsx
// 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>
  );
}

نصيحة

تختار الواجهة الأمامية تلقائياً الترجمة الصحيحة بناءً على اللغة النشطة للمستخدم. إذا لم تتوفر ترجمة، يتم عرض محتوى اللغة الافتراضية.

Released under the MIT License.