Control Panel Specifications
Data Model (form)
File-wide vs per-language fields. Trilingual publish gate. Every field with type, validation range, source (auto vs manual).
مكتمل
مرجع مختصر لكل حقل على سجلّ Founder File، مأخوذ مباشرة من الموديل الإنتاجي (`core/models/founder-file.model.ts`). ثلاث مجموعات: ميتاداتا الملف ككل (صفّ في `FounderFile`)، محتوى لكل لغة (1–3 صفوف في `FounderFileLang`)، والجداول الوسيطة / المرجعية. للمواصفات الكاملة بأنواع SQL وقواعد التحقّق ورسم الفورم، شوف Create spec.
حقول الملف ككل (صفّ واحد في `FounderFile`)
- id
- مفتاح أساسي للنظام (UUID). لا يُعدَّل من المستخدم أبداً.
- fileNumber
- رقم تسلسلي تحريري (INT IDENTITY). يظهر كـ "File #04" على الأغلفة + الكروت. مقفول بعد أول حفظ.
- slug
- يُولَّد تلقائياً من العنوان EN (kebab-case، ASCII-safe). فريد. يُقفل بعد خروج الحالة من `draft`.
- الحالة
- Enum (6 حالات): draft · ready-for-review · under-review · published · needs-updates · archived. شوف status-workflow.
- الفئة
- Enum اختيار واحد (إلزامي). 9 سلاسل تصنيفية: `business-fundamentals` · `funding-investment` · `startup-storytelling` · `growth-gtm` · `financial-literacy` · `legal-compliance` · `product-operations` · `leadership-teams` · `mena-market-insights`. مشترك مع تصنيف Articles.
- الموضوعات[]
- علاقة N:M مع جدول `Topic` (سلاسل slugs). الحد الأدنى 1، الأقصى 8. الكرت يعرض أول 3، صفحة التفاصيل تعرض كل الموضوعات. مشترك مع Articles.
- اللغات المتاحة[]
- مصفوفة رموز لغات (`en` / `ar` / `fr`). مُشتقّة من صفوف `FounderFileLang` الموجودة والمكتملة. تُغذّي شارات EN/AR/FR على الكروت + مبدّل اللغة العام.
- رابط صورة الغلاف
- رابط Azure Blob · 3:4 portrait · حد أدنى 600×800 · حد أقصى 5 MB · JPG/PNG/WebP. ترجع لـ SVG تحريري عند NULL/404.
- ogImageUrl؟
- صورة اختيارية 1.91:1 للمشاركة الاجتماعية. ترجع لـ `coverImageUrl` عند NULL.
- sponsorId؟
- FK اختياري لـ `FounderFileSponsor`. ON DELETE SET NULL. إن وُجد، عقد الراعي يجب أن يكون ساري عند الحفظ.
- isFeatured
- BIT (الافتراضي 0). يتحكّم في شارة "FEATURED" + spotlight section على الـ landing. ملف واحد فقط Featured لكل فئة في الوقت ذاته.
- isGated
- BIT (الافتراضي 1). لمّا 1، الضغط على Download يفتح مودال الموافقة (التقاط عميل). لمّا 0، PDF stream مباشر.
- isDownloadable
- BIT (الافتراضي 1). kill-switch رئيسي. لمّا 0، زرّ Download يختفي بالكامل من الصفحة العامة.
- وقت القراءة بالدقائق
- TINYINT، النطاق 1–120. تقدير تحريري يدوي. يظهر كشارة "{n} دقيقة قراءة" على الكروت + صفّ meta في التفاصيل.
- publishedAt
- DATETIME2 NULL. يُختم تلقائياً UTC عند أول انتقال لـ `published`. إعادة النشر لا تستبدله.
- lastUpdatedAt
- DATETIME2 NOT NULL. يقفز لـ GETUTCDATE() مع كل حفظ ناجح (أي حقل).
- archivedAt؟ / archivedBy؟
- ختم الحذف الناعم + الفاعل. NULL طالما live. يُملآن ذرّياً عند الأرشفة. قابل للاستعادة لمدة 90 يوم.
محتوى لكل لغة (1–3 صفوف في `FounderFileLang`)
PK مركّب = (`FounderFileId`, `Lang`). الملف يملك 1–3 صفوف (EN / AR / FR). بوّابة النشر الثلاثية تتطلّب اكتمال 3 صفوف قبل خروج الحالة من `draft`.
- title
- NVARCHAR(80) NOT NULL · 10–80 حرف · h1 على صفحة التفاصيل + عنوان كرت القائمة + ذيل breadcrumb + تبويب المتصفّح.
- subtitle
- NVARCHAR(160) NOT NULL · 30–160 حرف · الـ tagline اللي يظهر مباشرة تحت h1 على hero صفحة التفاصيل (و spotlight section على landing القائمة).
- shortDescription
- NVARCHAR(300) NOT NULL · 60–300 حرف · ملخّص 1–2 جملة يظهر كفقرة الجسم على كروت القائمة + يُستخدم احتياطياً كـ meta description لمّا `seoDescription` فاضي. ملاحظة: مختلف عن `subtitle` — `subtitle` هو الـ tagline الداخلي، `shortDescription` ملخّص مستوى الكرت.
- fullDescription
- NVARCHAR(MAX) NOT NULL · 100–600 حرف · نصّ فقط (HTML يُحذف عند الحفظ). يظهر كفقرة "لماذا هذا الملف مهم" على صفحة التفاصيل.
- bestForTagline؟
- NVARCHAR(120) NULL · 15–120 حرف · tagline تحريري قصير يظهر في كتلة "Best for" على كروت القائمة (مثل "Pre-raise self-audit"). كان hardcoded لكل ملف في i18n dictionary؛ الآن حقل CP حقيقي لكل لغة. الكرت يرجع للـ key القديم `ff.bestFor.<file.id>` لمّا الحقل فاضي.
- pdfUrl
- NVARCHAR(500) NOT NULL · مسار Azure Blob `founder-files/{slug}/{lang}/v{N}.pdf` · حد أقصى 50 MB · `application/pdf` فقط · مفحوص للفيروسات. آخر 5 نسخ محفوظة لكل لغة. ملاحظة: الموديل حالياً عنده `pdfUrl` واحد على الملف؛ هدف الـ migration per-language. شوف pdf-storage.
- fileSizeBytes
- BIGINT NOT NULL · يُحسب تلقائياً من الـ PDF المرفوع (على السيرفر). يظهر كشارة "{X} MB" في الـ CP بعد الرفع + (مستقبلاً) زرّ التحميل العام.
- pageCount
- INT NOT NULL · يُحسب تلقائياً من خطوة فحص الـ PDF. عرض فقط.
- seoTitle؟ / seoDescription؟
- NVARCHAR(120) NULL / NVARCHAR(300) NULL · override اختياري لـ SEO لكل لغة. لمّا NULL، الصفحة تستخدم `title` + `subtitle` لـ `<title>` + meta description.
- ogTitle؟ / ogDescription؟ / ogImageUrl؟ / canonicalUrl؟
- overrides Open Graph لكل لغة. كلها اختيارية. ترجع للقيم على مستوى الملف / `seoTitle`.
الجداول المرتبطة
- FounderFileTopic (وسيط N:M)
- وسيط بين `FounderFile` وجدول `Topic` المشترك. PK مركّب = (FounderFileId, TopicId). الكرت يعرض أول 3 موضوعات؛ التفاصيل تعرض الكل (حد أقصى 8 لكل ملف).
- FounderFileLearningPoint (1:N)
- صفّ لكل bullet في "What you'll learn" على صفحة التفاصيل. 3–5 صفوف لكل ملف. كل صفّ عنده `TextEn` + `TextAr` + `TextFr` (كل واحد NVARCHAR(100)، كلهم إلزامي). يظهر بالكامل على التفاصيل؛ أول 5 مكرَّرة على spotlight في الـ landing.
- FounderFileSponsor (مرجعي)
- سجلّ مستقلّ. الحقول: id، name، name_ar، tier (`sponsored-by` / `presented-by` / `in-partnership-with` / `powered-by`)، websiteUrl، logoUrl، shortDescription، shortDescription_ar، contractStart، contractEnd، isActive. مربوط من `FounderFile.sponsorId`.
- DownloadLead (1:N · سجلّ قانوني)
- صفّ لكل تحميل مسوَّر بالموافقة. يخزّن founderFileId (FK ON DELETE NO ACTION)، fullName، email، country، role (enum بـ 8 قيم)، primaryInterest (enum بـ 6 قيم)، companyName?، linkedinUrl?، startupStage?، industry?، websiteUrl?، language (CHAR(2))، consentNewsletter (BIT)، consentSponsor (BIT)، source (`listing`/`details`/`reader`/`direct`)، utmSource?/utmMedium?/utmCampaign?، downloadedAt (DATETIME2). احتفاظ 7 سنوات؛ لا يُحذَف نهائياً من الـ CP.
- FounderFileAuditLog (1:N)
- صفّ لكل انتقال حالة أو تغيير حقل قابل للتعديل. الحقول: id، founderFileId، occurredAt، actorId (FK→AdminUser)، eventType (`status-transition` / `field-change` / `review-picked-up` / `restored`)، fromValue، toValue، reason؟. تستخدمه شاشة Edit → تبويب History.
- FounderFileAnalyticsDay (1:N)
- عدّادات يومية مُجمَّعة لكل (ملف، يوم، لغة): views، reads، downloads، shares + 4 عدّادات نقرات CTA. تُغذّي خيارات الترتيب العام "الأكثر تحميلاً" / "الأكثر قراءة" + تقارير قمع العملاء في الـ admin.
