13 KiB
13 KiB
🏛️ المعمارية والاتفاقيات — diyaa.de
هاد الملف بيحدّد الشكل المستهدف للمشروع. أي feature جديد لازم يتبع هالاتفاقيات.
1) البنية العامة
الموقع بينقسم لـ واجهة عامة (public) + لوحة تحكم (admin) + API/Server Actions + قاعدة بيانات.
[ الزوّار ] ──> صفحات عامة (app/[locale]/…) ──┐
├──> Server Actions / Route Handlers ──> Prisma ──> PostgreSQL
[ المالك ] ──> لوحة التحكم (app/admin/…) ─────┘ │
ملفات مرفوعة (volume على الـ VPS)
2) أقسام الموقع (من رؤية المالك)
كل هالأقسام (عدا الألحان) = صفوف بجدول
Projectواحد تتفلتر بحقلtype. الفصل بالـ UI فقط.
- الأعمال / المعرض (Portfolio) —
type=PORTFOLIOبتصنيفات (شعارات، بروشورات، متاجر، أدوات ذكاء اصطناعي…). - التطبيقات (Apps) —
type=APPتطبيقات Swift، مع سكرين شوتس + حقول iOS (App Store، TestFlight، الإصدار، رابط الدعم، سياسة خصوصية التطبيق). - المواقع (Websites) —
type=WEBSITEمواقع Next.js/WordPress، مع سكرين شوتس ورابط حي. - التصاميم (Designs) —
type=DESIGN(أو ضمن PORTFOLIO) — شعارات، هويات بصرية. - الألحان (Melodies) — جدول
Melodyمنفصل، عرض وتشغيل بتصنيفات (عربية، شرقية…) + تحميل اختياري. - صفحات رسمية — سياسة الخصوصية، الشروط، Impressum (إلزامي قانونياً بألمانيا).
- تواصل — موجود، بنربطه بإرسال إيميل عبر SMTP + Honeypot + Rate limiting.
- لوحة تحكم — إضافة/تعديل/حذف كل ما سبق + التصنيفات.
3) مخطط قاعدة البيانات (Prisma) — الخيار A المعتمد
القرار (2026-08-05): جدول
Projectموحّد لكل الأعمال والتطبيقات والمواقع والتصاميم (يتميّزوا بحقلtype)، و**Melodyمنفصل** (طبيعته صوتية). التقنيات والسكرين شوتس كـString[](بدون جداول join)؛ نضيف جداولTechnology/Mediaمنفصلة لاحقاً فقط لو لزم فلترة بالتقنية أو إدارة alt-text متقدمة. التصنيف علاقة واحد-لواحد (مش many-to-many).⚠️ هاد يستبدل مخطط
Work/App/Melodyالقديم يلي انبنى بـ T-03/T-06. الاستبدال آمن لأنو ما في داتا إنتاجية بعد. شوف مهمة الـ refactor بـTODO.md.
enum ProjectType {
PORTFOLIO // شعار، بروشور، هوية بصرية… (سابقاً "أعمال/تصاميم")
APP // تطبيق Swift/iOS
WEBSITE // موقع Next.js أو WordPress
DESIGN // تصميم منفصل لو حبيت تفصله عن PORTFOLIO
}
enum CategoryKind {
PROJECT // تصنيفات المشاريع (شعارات، بروشورات، مواقع، متاجر، أدوات AI…)
MELODY // تصنيفات الألحان (عربي، شرقي…)
}
enum PublishStatus {
DRAFT
PUBLISHED
ARCHIVED
}
model Category {
id String @id @default(cuid())
kind CategoryKind
slug String @unique
nameAr String
nameEn String
order Int @default(0)
projects Project[]
melodies Melody[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Project { // جدول موحّد: أعمال + تطبيقات + مواقع + تصاميم
id String @id @default(cuid())
type ProjectType
slug String @unique
titleAr String
titleEn String
summaryAr String?
summaryEn String?
descAr String?
descEn String?
coverImage String? // مسار الغلاف
images String[] // معرض صور / سكرين شوتس
technologies String[] // التقنيات المستخدمة (بسيط، بدون جدول منفصل)
externalUrl String? // رابط المشروع/الموقع الحي
repoUrl String? // GitHub اختياري
// ── حقول خاصة بالتطبيقات (تبقى فاضية لغير APP) ──
platform String? // "iOS/Swift" | "Next.js" | "WordPress"…
appStoreUrl String?
testflightUrl String?
appVersion String?
supportUrl String?
appPrivacyUrl String?
// ── حالة وترتيب ──
status PublishStatus @default(DRAFT)
isFeatured Boolean @default(false)
sortOrder Int @default(0)
publishedAt DateTime?
categoryId String?
category Category? @relation(fields: [categoryId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type, status])
}
model Melody { // لحن (منفصل بسبب طبيعته الصوتية)
id String @id @default(cuid())
slug String @unique
titleAr String
titleEn String
descAr String?
descEn String?
audioFile String // مسار ملف الصوت
coverImage String? // artwork
durationSec Int?
isDownloadable Boolean @default(false) // سماح/منع التحميل
status PublishStatus @default(DRAFT)
isFeatured Boolean @default(false)
sortOrder Int @default(0)
categoryId String
category Category @relation(fields: [categoryId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ContactMessage { // رسائل نموذج التواصل
id String @id @default(cuid())
name String
email String
message String
createdAt DateTime @default(now())
}
enum AnalyticsEventType {
PAGE_VIEW
PROJECT_OPEN
MELODY_PLAY
PROJECT_LINK_CLICK
CONTACT_SUBMITTED
}
model AnalyticsEvent { // عدّادات داخلية بدون خدمة خارجية
id String @id @default(cuid())
type AnalyticsEventType
path String?
projectId String?
melodyId String?
createdAt DateTime @default(now())
}
model User { // مستخدم لوحة التحكم (أنت)
id String @id @default(cuid())
email String @unique
password String // hashed
createdAt DateTime @default(now())
}
// (مؤجّل) إعدادات الموقع من الأدمن — نضيفه عند الحاجة
// model SiteSetting { id String @id key String @unique value String }
4) اتفاقيات المجلدات (الشكل المستهدف)
diyaa.de/
├── app/
│ ├── [locale]/ # الموقع العام
│ │ ├── page.tsx # الرئيسية
│ │ ├── work/ # المعرض (PORTFOLIO) + [slug]
│ │ ├── apps/ # التطبيقات (APP) + [slug]
│ │ ├── websites/ # المواقع (WEBSITE) + [slug]
│ │ ├── designs/ # التصاميم (DESIGN) + [slug]
│ │ ├── melodies/ # الألحان + [slug]
│ │ ├── legal/ # privacy, terms, impressum
│ │ ├── about/ · contact/
│ ├── maintenance/ · coming-soon/ # صفحات الأوضاع
│ ├── admin/ # 🔒 لوحة التحكم (محميّة بـ Auth)
│ │ ├── projects/ · melodies/ · categories/ · settings/
│ │ └── layout.tsx
│ ├── api/ # Route Handlers (health, upload, …)
│ └── globals.css
├── components/
│ ├── ui/ # 🧩 shadcn/ui (مكوّنات أساسية)
│ ├── public/ # مكوّنات الواجهة العامة
│ ├── admin/ # مكوّنات لوحة التحكم
│ └── shared/ # مشتركة
├── lib/
│ ├── db.ts # Prisma client (singleton)
│ ├── auth.ts # إعداد Auth.js
│ ├── email.ts # Nodemailer + SMTP
│ ├── upload.ts # منطق رفع الملفات
│ ├── validations/ # مخططات Zod
│ ├── i18n.ts · site.ts · metadata.ts
├── prisma/
│ └── schema.prisma # 🎯 مصدر الحقيقة لبنية الداتا
├── content/ # قواميس الترجمة الثابتة (ar/en)
├── docs/ # 📚 التوثيق
└── (Docker, Makefile, …)
5) اتفاقيات المكوّنات (Components)
- كل شي كمكوّن قابل لإعادة الاستخدام. لا تكرّر UI.
- مكوّنات
components/ui/= shadcn الأساسية (Button, Card, Dialog, Input…). لا تعدّل سلوكها الأساسي، ابنِ فوقها. - مكوّنات الأقسام (ProjectCard مشترك لكل الأنواع + MelodyCard + AudioPlayer) بتعيش بـ
components/public/. - Server Components افتراضياً؛ استخدم
"use client"فقط عند الحاجة للتفاعل (مشغّل صوت، فورمات، ثيم). - كل مكوّن عام لازم يدعم العربي والإنجليزي ويحترم اتجاه RTL/LTR.
- الأنماط عبر Tailwind utilities + متغيّرات الثيم؛ لا CSS خام جديد (نحافظ على متغيّرات الثيم الموجودة لكن عبر إعداد Tailwind).
6) اتفاقيات Server Actions / API
- عمليات لوحة التحكم (إنشاء/تعديل/حذف) → Server Actions بـ فاليديشن Zod.
- كل action بيتحقق من الجلسة (Auth) قبل أي كتابة.
- endpoints العامة (رفع، health، ربما RSS للألحان) →
app/api/. - كل مدخلات المستخدم تمرّ بـ Zod schema من
lib/validations/. - نموذج التواصل: يتحمّى بـ Honeypot (حقل مخفي) + Rate limiting (تحديد عدد الطلبات) لمنع السبام.
- رفع الملفات: تحقّق من نوع الملف وحجمه، أسماء ملفات آمنة،
altTextللصور، وحذف الملف الفعلي من الـ volume عند حذف المحتوى.
7) الوسائط والملفات
- الملفات المرفوعة تُخزّن على volume بالـ VPS (مش داخل صورة Docker).
- الصور تُعالَج بـ
sharp(تصغير + webp). - مسارات الملفات تُخزّن كـ نصوص بالداتابيس (مش الملف نفسه).
- الرفع يتم عبر
app/api/upload/بعد التحقق من جلسة الأدمن، والملفات تُقدّم عبرapp/api/uploads/. - مسار التخزين قابل للضبط عبر
UPLOAD_DIR، والـ Docker volume الافتراضي هو/app/uploads.
8) الأوضاع (Site modes)
NEXT_PUBLIC_SITE_MODEبيقبل ثلاث قيم:full·maintenance·coming-soon.coming-soon: يعرضComingSoonPanelويخفي الأقسام (موجود حالياً).maintenance: يعرض صفحة صيانة (جديد — T-14).full: الموقع الكامل.
- ✅ T-17 منجز:
.env.exampleيستخدمfullبدلlive، وlib/site.tsيعتمدcoming-soon | maintenance | full. واجهة الصيانة الفعلية والاستثناء المرئي للأدمن ضمن T-14. - لوحة التحكم بتشتغل بغضّ النظر عن الوضع (محميّة بالـ Auth) عشان تحضّر المحتوى قبل الإطلاق.
9) البيئة والنشر
- بيضاف خدمة postgres لـ
docker-compose.yml+ volume للبيانات + volume للملفات المرفوعة. - الـ Makefile بينضاف عليه أوامر:
db-up,migrate,seed,studio(Prisma Studio). - migrations تنطبق تلقائياً عند النشر (خطوة بالـ start أو entrypoint).
- أسرار (SMTP, DB URL, AUTH secret) بـ
.envفقط — ولا تُرفع للـ git.