MongoDB: مشروع شامل

آخر تحديث: 2026-08-26

تُعد المشاريع المتكاملة أفضل وسيلة لتقييم نتائج التعلم — حيث يعمل هذا المقرر على بناء نظام شامل لمراجعة التجارة الإلكترونية من خلال ست وحدات دراسية، يربط بين جميع المفاهيم التي تمت تغطيتها في المراحل من 1 إلى 5.

1. نظرة عامة على المشروع

المشروع: نظام تقييم ShopHub البنية: Node.js + Express + Mongoose + MongoDB + JWT الميزات: مصادقة المستخدمين، إدارة المنتجات، التعليقات المتداخلة، التحليلات المجمعة، التحكم في الأذونات، النشر عبر Atlas

نهج تصميم بنية النظام: يعتمد نظام مراجعة التجارة الإلكترونية على بنية كلاسيكية ثلاثية المستويات — طبقة التوجيه (تعيين عناوين URL + البرمجيات الوسيطة)، وطبقة الأعمال (وحدة التحكم التي تتولى منطق معالجة الطلبات)، وطبقة البيانات (نموذج Mongoose الذي يحدد هياكل البيانات والتحقق من صحتها). ويتيح الفصل بين هذه الطبقات الثلاث اختبار كل طبقة وتعديلها بشكل مستقل: فلا يؤثر تغيير مسارات واجهة برمجة التطبيقات (API) على منطق الأعمال، كما لا يؤثر تغيير طرق الاستعلام على تعريفات التوجيه.

عملية اتخاذ القرار بشأن اختيار التصميم المعماري:

نقطة اتخاذ القرار الخيار أ الخيار ب الاختيار السبب
أطر عمل الويب Express Koa / Fastify Express تتمتع بأكثر النظم البيئية نضجًا وبأكبر عدد من موارد الدروس التعليمية
ODM Mongoose برنامج تشغيل أصلي Mongoose التحقق من صحة المخطط + البرامج الوسيطة + التعبئة
أنظمة المصادقة JWT الجلسة JWT لا تعتمد على الحالة، قابلة للتوسع بسهولة، مناسبة لواجهات برمجة التطبيقات
بنية التعليقات المستندات المتداخلة وضع الاقتباس وضع الاقتباس (parentId) عمق مرن، مع تجنب قيود التداخل
منصة النشر Atlas + Render استضافة ذاتية Atlas + Render خدمة مُدارة توفر تكاليف التشغيل والصيانة، ومناسبة للمشاريع الصغيرة والمتوسطة

قرارات نمذجة البيانات: اختار نظام التعليقات نموذجًا مرجعيًّا (حيث يشير parentId إلى التعليق الأصلي) بدلاً من المستندات المتداخلة — فالوثائق المتداخلة تخضع لحد BSON البالغ 16 ميغابايت، كما أن الردود المتداخلة بعمق يصعب الاستعلام عنها وتقسيمها إلى صفحات. وعلى الرغم من أن النموذج المرجعي يتطلب استعلامات إضافية لتجميع البنية الشجرية، فإنه يدعم مستويات غير محدودة وفرزًا مرنًا.

100%
graph TB
    Client[Browser/Mobile App] -->|HTTP| Express[Express Server]

    subgraph "Express Routing Layer"
        Express --> AuthMW[auth Routing<br/>Register/Log In]
        Express --> ProductMW[products Routing<br/>CRUD]
        Express --> ReviewMW[reviews Routing<br/>Comments/Likes]
    end

    subgraph "Controller Layer"
        AuthMW --> AuthCtrl[authController]
        ProductMW --> ProdCtrl[productController]
        ReviewMW --> RevCtrl[reviewController]
    end

    subgraph "Model Layer"
        AuthCtrl --> UserModel[User Model]
        ProdCtrl --> ProdModel[Product Model]
        RevCtrl --> RevModel[Review Model]
    end

    subgraph "MongoDB Gathering"
        UserModel --> Users[(users)]
        ProdModel --> Products[(products)]
        RevModel --> Reviews[(reviews)]
    end

    style Express fill:#d4edda
    style Reviews fill:#cce5ff

علاقات نموذج البيانات:

100%
erDiagram
    USER ||--o{ REVIEW : "writes"
    PRODUCT ||--o{ REVIEW : "has"
    REVIEW ||--o{ REVIEW : "parent reply"

    USER {
        ObjectId _id PK
        string email UK
        string username UK
        string passwordHash
        string role
        boolean isActive
    }
    PRODUCT {
        ObjectId _id PK
        string sku UK
        string title
        number price
        string category
        number rating
        number reviewCount
    }
    REVIEW {
        ObjectId _id PK
        ObjectId productId FK
        ObjectId userId FK
        string content
        number rating
        ObjectId parentId FK
        number likeCount
        boolean isApproved
    }

2. الوحدة 1: بدء المشروع

شرح مفصل لعملية اختيار البنية: لا يقتصر اختيار التكنولوجيا المناسبة لنظام المراجعة في التجارة الإلكترونية على «الاعتماد على ما هو شائع»، بل يتعلق بإيجاد الحل الأمثل بناءً على القيود — 1. اختيار Express بدلاً من NestJS: بالنسبة للمشاريع الصغيرة والمتوسطة الحجم، تضيف زخارف NestJS وحقن التبعية (DI) تعقيدًا دون تقديم فوائد إضافية؛ 2. اختر Mongoose بدلاً من برنامج التشغيل الأصلي: تعد آليات التحقق من صحة المخطط والبرمجيات الوسيطة أمراً بالغ الأهمية لنظام التقييم (تجزئة كلمة المرور، وتصفية الحذف المؤقت)؛ 3. اختر JWT بدلاً من الجلسات: تتطلب خدمات واجهة برمجة التطبيقات (API) تشغيلًا بدون حالة؛ بينما تتطلب الجلسات تخزين Redis مشتركًا، مما يزيد من التكاليف التشغيلية؛ 4. اختر النموذج المرجعي بدلاً من المستندات المتداخلة: لا يمكن التنبؤ بعدد التقييمات، كما أن المستندات المتداخلة تنطوي على خطر الوصول إلى الحد الأقصى البالغ 16 ميغابايت.

مبادئ تفاعل الوحدات: تشكل التبعيات بين الوحدات الست تسلسلاً هرميًا واضحًا — حيث توفر الوحدة 1 (التهيئة) البنية التحتية؛ وتحدد الوحدة 2 (النموذج) عقد البيانات؛ وتنفذ الوحدة 3 (CRUD) منطق الأعمال؛ وتضيف الوحدة 4 (التجميع) قدرات تحليلية؛ وتعزز الوحدة 5 (الأمان) الحماية؛ وتكمل الوحدة 6 (النشر) عملية النشر. تعتمد كل وحدة فقط على الوحدة التي تسبقها ولا توجد لها تبعيات إلى الوراء، مما يسمح بمواصلة التطوير بشكل تكراري — حيث يتم أولاً إكمال الوحدات 1–3 للحصول على واجهة برمجة تطبيقات (API) قابلة للاستخدام، ثم إضافة التجميع والأمن والنشر تدريجيًا.

مبادئ التصميم لبدء المشروع: لا يقتصر بدء المشروع على مجرد تشغيل الأمر "npm init" فحسب؛ بل هو أيضًا عملية لتحديد بنية المشروع، واختيار التبعيات، ووضع استراتيجيات لإدارة التكوين. يجب أن يعكس المشروع ذو البنية الجيدة بنية MVC: تعريفات البيانات في models/، والمنطق التجاري في controllers/، وتعيينات المسارات في routes/، والمسائل الشاملة (مثل المصادقة ومعالجة الأخطاء) في middlewares/.

مبادئ تصميم هياكل الدلائل:

جدول المحتويات المسؤوليات توجيهات التبعية استراتيجية الاختبار
النماذج/ تعريف البيانات + التحقق من صحتها عدم وجود تبعيات خارجية اختبارات الوحدة
وحدات التحكم/ معالجة الطلبات + التنسيق تعتمد على النماذج اختبار التكامل
المسارات/ تخطيط عناوين URL → وحدات التحكم التبعيات: وحدات التحكم + برامج الوساطة اختبار المسارات
البرامج الوسيطة/ المصادقة/التفويض/معالجة الأخطاء تكوين التبعيات الاختبار الوحدوي
أدوات التحقق تعريف مخطط joi عدم وجود تبعيات خارجية اختبارات الوحدة
utils/ وظائف مساعدة لا توجد تبعيات خارجية اختبارات الوحدة

أسباب اختيار التبعيات:

التبعية الغرض لماذا تختاره
Express إطار عمل ويب الأكثر نضجًا، مع نظام بيئي غني للبرمجيات الوسيطة
Mongoose ODM التحقق من صحة المخطط + التعبئة + البرمجيات الوسيطة
jsonwebtoken مصادقة JWT مصادقة بدون حالة، مناسبة لواجهات برمجة التطبيقات
bcrypt تجزئة كلمة المرور معيار صناعي، مقاوم لجداول قوس قزح
joi التحقق من صحة المدخلات على غرار المخطط، قابل لإعادة الاستخدام
dotenv متغيرات البيئة إرشادات تطبيقات الـ 12 عاملاً
cors عبر الأصول أساسيات واجهة برمجة التطبيقات

(1) هيكل المشروع

BASH
shophub-reviews/
├── package.json
├── .env
├── .env.example
├── src/
│   ├── app.js              # Express Applications
│   ├── config/
│   │   └── db.js          # mongoose Connect
│   ├── models/            # Data Model
│   │   ├── User.js
│   │   ├── Product.js
│   │   └── Review.js
│   ├── controllers/       # Business Logic
│   │   ├── authController.js
│   │   ├── productController.js
│   │   └── reviewController.js
│   ├── routes/            # Routing
│   │   ├── auth.js
│   │   ├── products.js
│   │   └── reviews.js
│   ├── middlewares/       # Middleware
│   │   ├── auth.js        # JWT Certification
│   │   ├── errorHandler.js
│   │   └── validate.js
│   ├── validators/        # Request for a Trial Certificate
│   │   └── schemas.js
│   └── utils/             # Tools
│       ├── logger.js
│       └── jwt.js
└── README.md

(2) package.json

JSON
{
  "name": "shophub-reviews",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js",
    "test": "jest --watch"
  },
  "dependencies": {
    "express": "^4.19.2",
    "mongoose": "^7.6.0",
    "jsonwebtoken": "^9.0.0",
    "bcrypt": "^5.1.0",
    "joi": "^17.13.0",
    "dotenv": "^16.4.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.1.0",
    "jest": "^29.7.0"
  }
}

(3) ملف .env

BASH
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/shophub
JWT_SECRET=your-super-secret-key-change-in-prod
JWT_EXPIRES_IN=7d


3. الوحدة 2: نماذج المستخدم والمنتج

مبادئ تصميم نموذج البيانات: لا يقتصر تصميم المخطط على مجرد «نسخ حقول الجدول إلى Mongoose»؛ بل يتطلب مراعاة: أنماط الاستعلامات (ما هي الاستعلامات الأكثر شيوعًا؟)، وعلاقات البيانات (ما هي الكيانات التي يجب ربطها ببعضها؟)، ومتطلبات الأداء (هل هناك حاجة إلى الفهارس؟ هل يجب تعيين select:false لحقول معينة؟)، ومتطلبات الأمان (هل يجب إخفاء كلمات المرور؟ هل هناك حاجة إلى الحذف المؤقت؟).

قرارات تصميم المخطط:

قرارات التصميم الاختيار السبب
تخزين كلمة المرور passwordHash + select:false لا يتم إرجاعها في الاستعلامات الافتراضية لمنع الكشف عنها
تجزئة كلمة المرور برمجيات وسيطة قبل الحفظ + bcrypt تجزئة تلقائية؛ شفافة بالنسبة لمنطق الأعمال
تصميم الأدوار قائمة التعداد + RBAC ثلاثة أدوار: عميل/مسؤول/مشرف
تقييم المنتج الحقول المكررة: التقييم + عدد التعليقات تجنب حساب المجموع في كل مرة
بنية التعليق مرجع parentId يدعم مستويات تداخل غير محدودة
الحذف المؤقت isDeleted + التصفية المسبقة البيانات قابلة للاستعادة؛ متطلبات الامتثال
الطوابع الزمنية timestamps: true الإدارة التلقائية لـ createdAt/updatedAt

علاقات النموذج واتجاهات الإشارة: تشير «المراجعة» إلى «مستخدم» و«منتج» (علاقة «كثير إلى واحد»)، كما تشير «المراجعة» إلى نفسها (تتيح الإشارة الذاتية إمكانية التداخل). واتجاه الإشارة هو «الجانب الذي يمثل علاقة "كثير إلى واحد" يشير إلى الجانب الذي يمثل علاقة "واحد إلى كثير"» — أي أنه بدلاً من تضمين مصفوفة من «المراجعات» داخل «المنتج» (مما قد يؤدي إلى نمو لا نهائي)، تقوم «المراجعة» بتخزين معرّف المنتج (productId).

100%
graph LR
    User1[Alice] -->|userId| R1[Review: "Great!"]
    User2[Bob] -->|userId| R2[Review: "Good"]
    User3[Charlie] -->|userId| R3[Reply: "Thanks!"]
    
    Prod1[Product: Phone] -->|productId| R1
    Prod1 -->|productId| R2
    R1 -->|parentId| R3

    style User1 fill:#cce5ff
    style Prod1 fill:#d4edda
    style R1 fill:#fff3cd

(1) نموذج المستخدم (بما في ذلك البرمجيات الوسيطة لتشفير كلمات المرور)

JAVASCRIPT
// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true,
    trim: true,
    match: [/^\S+@\S+\.\S+$/, 'Invalid email format']
  },
  username: {
    type: String,
    required: true,
    unique: true,
    minlength: 3,
    maxlength: 30,
    match: [/^[a-zA-Z0-9_]+$/, 'Alphanumeric + underscore only']
  },
  passwordHash: {
    type: String,
    required: true,
    minlength: 60,  // bcrypt hash Length
    select: false
  },
  role: {
    type: String,
    enum: ['customer', 'admin', 'moderator'],
    default: 'customer'
  },
  isActive: { type: Boolean, default: true }
}, { timestamps: true });

// Password Hashing Middleware
UserSchema.pre('save', async function(next) {
  if (!this.isModified('passwordHash')) return next();
  this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
  next();
});

// Confirm Password
UserSchema.methods.comparePassword = function(candidate) {
  return bcrypt.compare(candidate, this.passwordHash);
};

module.exports = mongoose.model('User', UserSchema);

(2) طراز المنتج

JAVASCRIPT
// models/Product.js
const ProductSchema = new mongoose.Schema({
  sku: { type: String, required: true, unique: true, index: true },
  title: { type: String, required: true, maxlength: 200 },
  description: { type: String, maxlength: 5000 },
  price: { type: mongoose.Schema.Types.Decimal128, required: true, min: 0 },
  category: { type: String, enum: ['Electronics', 'Books', 'Clothing', 'Home'], index: true },
  stock: { type: Number, default: 0, min: 0 },
  rating: { type: Number, default: 0, min: 0, max: 5 },
  reviewCount: { type: Number, default: 0 },
  isActive: { type: Boolean, default: true, index: true }
}, { timestamps: true });

ProductSchema.index({ category: 1, price: -1 });
ProductSchema.index({ title: 'text', description: 'text' });

module.exports = mongoose.model('Product', ProductSchema);

(3) نموذج المراجعة (بما في ذلك الردود المتداخلة)

JAVASCRIPT
// models/Review.js
const ReviewSchema = new mongoose.Schema({
  productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true, index: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  content: { type: String, required: true, maxlength: 1000 },
  rating: { type: Number, required: true, min: 1, max: 5 },
  parentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Review', default: null, index: true },
  likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  likeCount: { type: Number, default: 0 },
  isApproved: { type: Boolean, default: true },
  isDeleted: { type: Boolean, default: false }
}, { timestamps: true });

ReviewSchema.index({ productId: 1, createdAt: -1 });
ReviewSchema.pre(/^find/, function(next) {
  this.where({ isDeleted: { $ne: true } });
  next();
});

module.exports = mongoose.model('Review', ReviewSchema);


4. الوحدة 3: واجهة برمجة تطبيقات CRUD للتعليقات (التعليقات المتداخلة)

ملاحظات إضافية حول مبادئ تصميم التعليقات المتداخلة: تتطلب استراتيجية الاستعلام الخاصة بالنموذج المرجعي (parentId) تحقيق التوازن بين «عدد الاستعلامات» و«سلامة البيانات» — حيث تتطلب طريقة الاستعلامين (التعليقات من المستوى الأعلى + الردود) عمليتي إدخال/إخراج فقط من قاعدة البيانات، لكنها لا تدعم سوى مستويين من التداخل؛ في حين تدعم طريقة الاستعلام التكراري ($graphLookup) عددًا غير محدود من المستويات، لكن أداءها ضعيف. يختار هذا النظام طريقة الاستعلامين مقترنة بالتجميع داخل الذاكرة للأسباب التالية: 1. يكتفي معظم المستخدمين بعرض مستويين فقط من التعليقات (المستوى الأعلى + الردود)؛ 2. يمكن تحميل الردود الأعمق عند الطلب عبر زر «عرض المزيد من الردود»؛ 3. يتفوق أداء طريقة الاستعلامين بشكل كبير على أداء الاستعلام التكراري.

تصميم سياسة أمان التعليقات: تواجه أنظمة التعليقات ثلاثة أنواع من التهديدات الأمنية — 1. أمان المحتوى: التعليقات غير المرغوب فيها، والكلمات المفتاحية الحساسة، والروابط الإعلانية (الحماية: تحديد معدل الاستخدام + تصفية الكلمات المفتاحية الحساسة + المراقبة اليدوية)؛ 2. أمن الأذونات: قيام غير المؤلفين بتحرير أو حذف تعليقات الآخرين (الحماية: المصادقة + مطابقة معرف المستخدم)؛ 3. أمن الحقن: حقن $where، و$regex DoS (الحماية: الاستعلامات المعلمة + الهروب من التعبيرات النمطية). يجب تنفيذ الإجراءات الأمنية بشكل موحد في طبقة البرمجيات الوسيطة، بدلاً من تكرارها في كل وحدة تحكم.

100%
graph TB
    subgraph "Nested Comments (Threaded Comments)"
        A[Review<br/>_id: ObjectId] --> B[Reply 1<br/>parentId: A._id]
        A --> C[Reply 2<br/>parentId: A._id]
        B --> D[Reply to Reply<br/>parentId: B._id]
        C --> E[Reply to Reply<br/>parentId: C._id]
    end

    style A fill:#d4edda
    style B fill:#fff3cd
    style C fill:#fff3cd
    style D fill:#f8d7da
    style E fill:#f8d7da

يكمن التحدي الأساسي للتعليقات المتداخلة (التعليقات المتسلسلة) في كيفية تمثيل علاقة «الرد». هناك نهجان: (1) المستندات المضمنة (تضمين مصفوفة الردود داخل «Review») — وهي طريقة بسيطة لكنها مقيدة بحد BSON البالغ 16 ميغابايت، كما يصعب ترقيم صفحات الردود المتداخلة؛ (2) نمط الإشارة (يشير «parentId» إلى التعليق الأصلي) — وهو نهج مرن، ويدعم التداخل غير المحدود، ويدعم ترقيم الصفحات، لكنه يتطلب استعلامات إضافية لتجميع البنية الشجرية. يستخدم هذا المشروع نمط الإشارة.

استراتيجية الاستعلام عن التعليقات المتداخلة:

100%
sequenceDiagram
    Client Participant
    participant API as /products/:id/reviews
    participant DB as MongoDB

    Client->>API: GET /products/123/reviews
    API->>DB: Query top-level comments (parentId=null)
    DB-->>API: Returns [R1, R2, R3]
    API->>DB: Query all replies (parentId in [R1, R2, R3]._id)
    DB-->>API: Returns [R1.1, R1.2, R2.1]
    API->>API: Building a tree structure<br/>R1.replies=[R1.1, R1.2]<br/>R2.replies=[R2.1]
    API-->>Client: Returns tree-structured data

مراجعة تصميم واجهة برمجة التطبيقات (API) لعمليات CRUD:

العملية نقطة النهاية الطريقة المصادقة القواعد التجارية
عرض التقييمات /products/:id/reviews GET لا ترقيم الصفحات + الردود المتداخلة
مراجعة المنشور /products/:id/reviews منشور نعم التحقق من وجود المنتج + التقييم من 1 إلى 5
نشر رد /products/:id/reviews نشر نعم التحقق من وجود التقييم الأصلي
تعديل المراجعة /reviews/:id PUT نعم (المؤلف) يمكن للمؤلف فقط تعديلها
حذف التعليق /reviews/:id حذف نعم (المؤلف/المسؤول) الحذف المؤقت isDeleted
الإعجاب/إلغاء الإعجاب /reviews/:id/like POST نعم التبديل باستخدام $addToSet/$pull

تستخدم ميزة «الإعجاب» الدالتين $addToSet (إضافة متكررة) و$pull (إزالة) بدلاً من الدالة push (التي قد تؤدي إلى تكرار الإعجابات). كما تحتفظ بحقل إضافي يُسمى likeCount لتجنب إعادة حساب عدد الإعجابات في كل مرة.

(1) إنشاء تقييم

JAVASCRIPT
// controllers/reviewController.js
exports.createReview = async (req, res) => {
  const { productId } = req.params;
  const { content, rating, parentId } = req.body;

  // Verify that the product exists
  const product = await Product.findById(productId);
  if (!product) return res.status(404).json({ error: 'Product not found' });

  // If this is a reply, verify that the parent comment exists
  if (parentId) {
    const parent = await Review.findById(parentId);
    if (!parent) return res.status(404).json({ error: 'Parent review not found' });
  }

  const review = await Review.create({
    productId,
    userId: req.user._id,
    content,
    rating,
    parentId: parentId || null
  });

  // Update product rating statistics
  await updateProductRating(productId);

  await review.populate('userId', 'username avatar');
  res.status(201).json(review);
};

(2) الاستعلام المتداخل

JAVASCRIPT
exports.listReviews = async (req, res) => {
  const { productId } = req.params;
  const { sort = 'createdAt', order = 'desc', limit = 20, page = 1 } = req.query;

  // Query top-level comments
  const reviews = await Review.find({
    productId,
    parentId: null
  })
    .populate('userId', 'username avatar')
    .sort({ [sort]: order === 'desc' ? -1 : 1 })
    .limit(limit * 1)
    .skip((page - 1) * limit)
    Continue

  // View all replies
  const reviewIds = reviews.map(r => r._id);
  const replies = await Review.find({
    parentId: { $in: reviewIds }
  })
    .populate('userId', 'username avatar')
    .sort({ createdAt: 1 })
    Continue

  // Build a tree structure
  const tree = reviews.map(parent => ({
    ...parent,
    replies: replies.filter(r => r.parentId.toString() === parent._id.toString())
  }));

  res.json({ data: tree, page, limit });
};

(3) ميزة «الإعجاب»

JAVASCRIPT
exports.likeReview = async (req, res) => {
  const { reviewId } = req.params;
  const userId = req.user._id;

  const review = await Review.findById(reviewId);
  if (!review) return res.status(404).json({ error: 'Review not found' });

  const alreadyLiked = review.likes.some(id => id.toString() === userId.toString());

  if (alreadyLiked) {
    await Review.updateOne(
      { _id: reviewId },
      { $pull: { likes: userId }, $inc: { likeCount: -1 } }
    );
    return res.json({ liked: false });
  } else {
    await Review.updateOne(
      { _id: reviewId },
      { $addToSet: { likes: userId }, $inc: { likeCount: 1 } }
    );
    return res.json({ liked: true });
  }
};


5. الوحدة 4: تحليل التجميع

تفاصيل نمط تصميم مسار التجميع: تنقسم متطلبات التجميع في نظام المراجعات إلى ثلاث فئات: 1. الإحصائيات أحادية البعد (توزيع التقييمات، عدد المراجعات): مسار واحد باستخدام $match + $group/$bucket؛ 2. الإحصائيات المتوازية متعددة الأبعاد (نظرة عامة على صفحة تفاصيل المنتج + التوزيع + المراجعات الحديثة): يجب استخدام $facet لإرجاع النتائج في استعلام واحد؛ 3. الإحصائيات المرتبطة عبر المجموعات (تصنيفات المنتجات الشائعة مع معلومات المنتج): مزيج من $group + $lookup + $unwind. مبدأ التصميم: أولاً $match لتصفية البيانات وتقليل حجمها، ثم $group للتجميع، وأخيراً $sort/$limit للفرز وتحديد العدد.

استراتيجية تصميم الأنابيب الفرعية في $facet: يجب أن تكون كل أنبوب فرعي في $facet بسيطًا قدر الإمكان: 1. لا يحتاج الأنبوب الفرعي الخاص بالعدد الإجمالي سوى إلى $count، مع الحد الأدنى من الأعباء الإضافية؛ 2. تستخدم خط الأنابيب الفرعي الخاص بـ "المتوسط" $group({ _id: null })، مما ينتج مخرجات واحدة؛ 3. يستخدم خط الأنابيب الفرعي الخاص بـ "التوزيع" $group({ _id: '$rating' })، مع عدد مخرجات يساوي حجم مجال القيمة؛ 4. يتطلب خط الأنابيب الفرعي الخاص بـ "القائمة" $sort + $limit + $lookup، مع أعلى تكلفة إضافية، ويجب وضعه في النهاية. لا يؤثر ترتيب خطوط الأنابيب الفرعية على التنفيذ (المتوازي)، ولكنه يؤثر على قابلية قراءة الكود — يُنصح بترتيبها من الأقل تكلفة إلى الأكثر تكلفة.

100%
graph LR
    subgraph "Aggregation Pipeline"
        A[Raw Data<br/>1M reviews] -->|"$match"| B[Filtered Data<br/>100K reviews]
        B -->|"$group"| C[Aggregated<br/>grouped]
        C -->|"$sort + $limit"| D[Top Results<br/>top 10]
    end

    style A fill:#f8d7da
    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#d4edda

يحتاج نظام التقييم إلى إحصائيات متعددة الأبعاد — توزيع التقييمات (عدد التقييمات ذات 5 نجوم/4 نجوم)، ومتوسط التقييم، وتصنيفات المنتجات الأكثر شعبية، واتجاهات التقييمات الحديثة. إذا تم حساب هذه الإحصائيات في كود طبقة التطبيق، فسيتعين نقل كميات كبيرة من البيانات إلى Node.js لمعالجتها؛ أما باستخدام مسارات التجميع، فيتم إجراء الحساب في طبقة قاعدة البيانات ولا يتم إرجاع سوى النتائج — ويمكن أن يصل الفرق في الأداء إلى 100 ضعف.

نمط تصميم المجمع:

المتطلبات الإحصائية مرحلة التجميع النتائج
توزيع النتائج $match → $bucket [{_id: 5, count: 120}, ...]
إحصائيات المنتج $match → $facet {الإجمالي، متوسط التقييم، التوزيع، الأحدث}
المنتجات الأكثر رواجًا $match → $group → $sort → $lookup → $limit [{productId, avgRating, reviewCount}]

قيمة $facet: تتيح $facet تشغيل عدة مسارات تجميع بالتوازي على نفس المدخلات — حيث يُرجع استعلام واحد أربعة أبعاد: total، و avgRating، و ratingDistribution، و recentReviews. وبدون $facet، سيكون من الضروري إجراء أربعة استعلامات منفصلة.

100%
graph TB
    Input[Comment Data Stream] --> Facet["$facet<br/>Multi-channel parallel processing"]
    
    Facet --> P1["Pipeline1: $count<br/>Total"]
    Facet --> P2["Pipeline2: $group<br/>Average Rating"]
    Facet --> P3["Pipeline3: $group + $sort<br/>Score Distribution"]
    Facet --> P4["Pipeline4: $sort + $limit + $lookup<br/>Recent Comments(Contains user information)"]
    
    P1 --> Output[Combined Output<br/>{total, avg, distribution, recent}]
    P2 --> Output
    P3 --> Output
    P4 --> Output

    style Facet fill:#d4edda
    style Output fill:#cce5ff

كيفية عمل توزيع الدرجات في $bucket: يقسم $bucket الدرجات إلى مجموعات بناءً على حدود — حيث تمثل الحدود = [1,2,3,4,5,6] خمس مجموعات: [1,2)، [2,3)، [3,4)، [4,5)، [5,6). ويتم حساب العدد والمتوسط لكل مجموعة.

(1) توزيع الدرجات ($bucket)

JAVASCRIPT
exports.getRatingDistribution = async (req, res) => {
  const { productId } = req.params;

  const distribution = await Review.aggregate([
    { $match: { productId: new mongoose.Types.ObjectId(productId), parentId: null } },
    {
      $bucket: {
        groupBy: '$rating',
        boundaries: [1, 2, 3, 4, 5, 6],
        default: 'Other',
        output: {
          count: { $sum: 1 },
          avgHelpful: { $avg: '$likeCount' }
        }
      }
    }
  ]);

  res.json({ data: distribution });
};

(2) إحصائيات المنتج ($facet)

JAVASCRIPT
exports.getProductStats = async (req, res) => {
  const { productId } = req.params;

  const stats = await Review.aggregate([
    { $match: { productId: new mongoose.Types.ObjectId(productId), parentId: null } },
    {
      $facet: {
        total: [{ $count: 'count' }],
        avgRating: [{ $group: { _id: null, avg: { $avg: '$rating' } } }],
        ratingDistribution: [
          { $group: { _id: '$rating', count: { $sum: 1 } } },
          { $sort: { _id: 1 } }
        ],
        recentReviews: [
          { $sort: { createdAt: -1 } },
          { $limit: 5 },
          {
            $lookup: {
              from: 'users',
              localField: 'userId',
              foreignField: '_id',
              as: 'userInfo'
            }
          },
          { $unwind: '$userInfo' },
          {
            $project: {
              content: 1,
              rating: 1,
              createdAt: 1,
              username: '$userInfo.username',
              avatar: '$userInfo.avatar'
            }
          }
        ]
      }
    }
  ]);

  res.json(stats[0]);
};

(3) أفضل المنتجات

JAVASCRIPT
exports.getTopProducts = async (req, res) => {
  const { limit = 10 } = req.query;

  const topProducts = await Review.aggregate([
    { $match: { parentId: null, isApproved: true } },
    {
      $group: {
        _id: '$productId',
        avgRating: { $avg: '$rating' },
        reviewCount: { $sum: 1 },
        totalLikes: { $sum: '$likeCount' }
      }
    },
    { $sort: { avgRating: -1, reviewCount: -1 } },
    { $limit: limit * 1 },
    {
      $lookup: {
        from: 'products',
        localField: '_id',
        foreignField: '_id',
        as: 'product'
      }
    },
    { $unwind: '$product' },
    {
      $project: {
        sku: '$product.sku',
        title: '$product.title',
        thumbnail: '$product.thumbnail',
        avgRating: 1,
        reviewCount: 1
      }
    }
  ]);

  res.json({ data: topProducts });
};


6. الوحدة 5: الأذونات والأمان

شرح مفصل لمبدأ الدفاع المتعدد الطبقات: أمن واجهة برمجة التطبيقات (API) ليس نقطة دفاع واحدة، بل نظام دفاع متعدد الطبقات — حيث تمنع طبقة الشبكة (HTTPS + CORS) التنصت وإساءة استخدام عبر الأصول؛ وتقوم طبقة التطبيق (مصادقة JWT + تفويض RBAC + التحقق من صحة المدخلات) باعتراض الطلبات غير المصرح بها والخبيثة؛ وتمنع طبقة البيانات (الاستعلامات المعلمة + مستخدمو قاعدة البيانات ذوو الامتيازات الدنيا) هجمات الحقن والوصول غير المصرح به. أهمية الدفاع المستقل في كل طبقة: لا يؤدي اختراق أي طبقة بمفردها إلى المساس بالحماية التي توفرها الطبقات الأخرى — فحتى في حالة التكوين الخاطئ لـ CORS، لا يزال بإمكان مصادقة JWT حظر المستخدمين غير المصادق عليهم؛ وحتى في حالة اختراق JWT، لا يزال بإمكان RBAC تقييد نطاق العمليات للمستخدمين ذوي الامتيازات المنخفضة.

أفضل الممارسات الأمنية لرموز JWT: 1. قوة المفتاح: إنشاء مفتاح عشوائي 256 بت باستخدام openssl rand -hex 32؛ 2. مدة الصلاحية: 7 أيام (لتحقيق التوازن بين الأمان وتجربة المستخدم؛ ويمكن تقصير هذه المدة للعمليات الحساسة)؛ 3. تحديث الرمز: مدة صلاحية قصيرة لرموز الوصول + مدة صلاحية طويلة لرموز التحديث (آلية الرمز المزدوج)؛ 4. تخزين الرموز: استخدام ملفات تعريف الارتباط httpOnly (لمنع هجمات XSS) في الواجهة الأمامية بدلاً من localStorage؛ 5. إلغاء الرموز: الاحتفاظ بقائمة سوداء (Redis SET) أو استخدام فترات صلاحية قصيرة لتقليل الحاجة إلى الإلغاء.

100%
graph TB
    subgraph "Defense in Depth"
        L1[Network Layer<br/>TLS + CORS] --> L2[Application Layer<br/>Auth + Authorization + Input Validation]
        L2 --> L3[Data Layer<br/>Parameterized Queries + Least Privilege]
    end

    style L1 fill:#d4edda
    style L2 fill:#fff3cd
    style L3 fill:#f8d7da

يتبع أمن واجهة برمجة التطبيقات (API) مبدأ «الدفاع المتعدد المستويات» — أي ليس خط دفاع واحد، بل طبقات متعددة: طبقة الشبكة (TLS/CORS) → طبقة التطبيق (المصادقة + التفويض + التحقق من صحة المدخلات) → طبقة البيانات (استعلامات معلمة + مبدأ أقل الامتيازات). وتقوم كل طبقة بالدفاع بشكل مستقل؛ فإذا تم اختراق إحدى الطبقات، لا تتأثر الطبقات الأخرى.

المصادقة عبر JWT مقابل المصادقة عبر الجلسة:

البعد JWT الجلسة
موقع التخزين العميل (الرمز المميز) الخادم (الذاكرة/Redis)
قابلية التوسع لا تعتمد على الحالة، وتدعم التوزيع بشكل طبيعي تتطلب تخزينًا مشتركًا للجلسات
الأمان لا يمكن إلغاء تسرب الرمز المميز على الفور يمكن إنهاء الجلسة على الفور
الأداء لا توجد أعباء إضافية على جانب الخادم يتم الاستعلام عن تخزين الجلسة في كل مرة
إدارة انتهاء الصلاحية مطالبة بانتهاء الصلاحية، لا يمكن التجديد بشكل استباقي يمكن تجديدها مع انتهاء صلاحية متدرج
السيناريوهات المناسبة واجهة برمجة التطبيقات (API) / الخدمات الصغيرة تطبيقات الويب التقليدية

نموذج أذونات RBAC: يمنح نظام التحكم في الوصول القائم على الأدوار (RBAC) الأذونات بناءً على الأدوار — فالمستخدمون لديهم أدوار، والأدوار لها أذونات. ويضم هذا النظام ثلاثة أدوار:

الدور الصلاحيات النطاق
العميل الاطلاع على التقييمات، والإعجاب بها، وتعديل التقييمات الخاصة /reviews (الخاصة)
المشرف مراجعة/حذف أي تعليقات /التعليقات (الكل) + لوحة التحكم الخاصة بالإشراف
admin إدارة المنتجات والمستخدمين وجميع التقييمات /products + /users + /reviews (الكل)

أساسيات الحماية من هجمات الحقن: تنشأ مخاطر هجمات الحقن في MongoDB بشكل أساسي من $where (التي تُنفِّذ جافا سكريبت تعسفي) و$regex غير المثبتة (هجمات DoS). مبادئ الحماية: (1) استخدم دائمًا الاستعلامات المعلمة بدلاً من تسلسل السلاسل؛ (2) يقوم Mongoose تلقائيًا بتشفير قيم الاستعلام؛ (3) تتطلب $regex تشفير الأحرف الخاصة.

100%
sequenceDiagram
    Client Participant
    participant Auth as authenticate Middleware
    participant Authorize as authorize Middleware
    participant Controller
    DB participant

    Client->>Auth: Request + Bearer Token
    Auth->>Auth: jwt.verify(token, secret)
    alt Token is invalid
        Auth-->>Client: 401 Unauthorized
    else Token Valid
        Auth->>Authorize: req.user = {id, role}
        Authorize->>Authorize: roles.includes(req.user.role)?
        alt: No permission
            Authorize-->>Client: 403 Forbidden
        else has permission
            Authorize->>Controller: Execute business logic
            Controller >> DB: Parameterized Queries
            DB-->>Client: 200 OK
        than
    than

(1) برمجيات الوسيطة JWT

JAVASCRIPT
// middlewares/auth.js
const jwt = require('jsonwebtoken');

exports.authenticate = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'No token' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

exports.authorize = (...roles) => (req, res, next) => {
  if (!req.user || !roles.includes(req.user.role)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

(2) واجهة برمجة تطبيقات المصادقة

JAVASCRIPT
// controllers/authController.js
const User = require('../models/User');
const jwt = require('jsonwebtoken');

exports.register = async (req, res) => {
  const { email, username, password } = req.body;

  const existing = await User.findOne({ $or: [{ email }, { username }] });
  if (existing) return res.status(409).json({ error: 'Email or username already exists' });

  const user = await User.create({
    email,
    username,
    passwordHash: password  // pre-save The middleware performs hashing
  });

  const token = jwt.sign(
    { id: user._id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.status(201).json({ user: { id: user._id, email: user.email, username: user.username }, token });
};

exports.login = async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email }).select('+passwordHash');
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  const valid = await user.comparePassword(password);
  if (!valid) return res.status(401).json({ error: 'Invalid credentials' });

  const token = jwt.sign(
    { id: user._id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  res.json({ user: { id: user._id, email: user.email, username: user.username, role: user.role }, token });
};

(3) الحماية من الحقن

JAVASCRIPT
// ❌ Danger: $where executes arbitrary JavaScript
db.reviews.find({ $where: 'this.userId == "' + userId + '"' });

// ✅ Security: Use parameterized queries
db.reviews.find({ userId: new ObjectId(userId) });

// ✅ Mongoose auto-escapes
const reviews = await Review.find({ userId: userId });

// ❌ Danger: $regex DoS
db.reviews.find({ content: { $regex: req.query.q } });

// ✅ Security: Escape special characters in regular expressions
function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
db.reviews.find({ content: { $regex: escapeRegex(req.query.q), $options: 'i' } });


7. الوحدة 6: النشر

قرار بنية النشر: يعتمد اختيار استراتيجية النشر على حجم المشروع وقدرات الفريق: 1. Atlas + Render (الخيار المختار في هذا المشروع): بدء تشغيل بدون أي عمليات (zero-ops)، مناسب للمشاريع الصغيرة إلى المتوسطة ومراحل النموذج القابل للتطبيق (MVP)، التكلفة الشهرية أقل من 50 دولارًا؛ 2. MongoDB ذاتي الاستضافة + Docker: قابل للتحكم الكامل ولكنه يتطلب قدرات DevOps، ومناسب للمشاريع المتوسطة إلى الكبيرة التي لديها فريق DevOps؛ 3. Kubernetes + Atlas: التوسع المرن + قاعدة بيانات مُدارة، ومناسب لأنظمة الإنتاج ذات حركة المرور المتقلبة. يختار هذا المشروع Atlas + Render للسبب الأساسي التالي: تركيز الطاقة المحدودة على تطوير الأعمال بدلاً من العمليات.

فحص الحالة والاستعادة التلقائية: لا تقتصر فحوصات حالة النشر في بيئة الإنتاج على مجرد إرجاع الرمز 200 عند استدعاء /healthz — بل يجب أن تتحقق من ثلاث طبقات من الحالة: 1. طبقة التطبيق (هل يستجيب Express؟)؛ 2. طبقة قاعدة البيانات (هل يمكن الوصول إلى MongoDB؟)؛ 3. طبقة التبعيات (هل يعمل Redis/ES بشكل طبيعي؟). يتم استدعاء نقطة نهاية فحص الحالة بشكل دوري بواسطة موزع الحمل؛ وبعد N حالات فشل متتالية، تتم إزالة المثيل وإعادة تشغيله تلقائيًا. عند انقطاع الاتصال بقاعدة البيانات، يجب أن تُرجع النتيجة 503 (الخدمة غير متاحة) بدلاً من 200، لتجنب إرسال حركة المرور إلى المثيلات غير السليمة.

100%
graph TB
    subgraph "Deployment Architecture"
        A[Client] --> B[Render<br/>Node.js App]
        B --> C[Atlas<br/>MongoDB Cluster]
        B --> D[CDN<br/>Static Assets]
    end

    style A fill:#d4edda
    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#fff3cd

لا يقتصر النشر على مجرد «git push» ثم ينتهي الأمر — بل يتطلب مراعاة عدة أمور: عزل البيئات (التطوير/الاختبار/الإنتاج)، وإدارة الأسرار (عدم تخزين كلمات المرور في الكود)، وفحوصات الحالة (إعادة التشغيل التلقائي للمثيلات التي تعاني من مشاكل)، وأمن البيانات (تشفير TLS + النسخ الاحتياطي والاستعادة). استراتيجية النشر الخاصة بهذا المشروع: قاعدة بيانات مُدارة بواسطة Atlas + تطبيق مُدار بواسطة Render/Railway — شركة ناشئة لا تتطلب أي عمليات تشغيل، ومناسبة للمشاريع الصغيرة والمتوسطة.

بنية النشر:

100%
graph LR
    Client[User's browser] -->|HTTPS| CDN[CDN / Static Resources]
    Client -->|HTTPS| LB[Load Balancer]
    LB -->|HTTP| App1[Render Instance 1<br/>Express App]
    LB -->|HTTP| App2[Render Instance 2<br/>Express App]
    App1 -->|TLS + SRV| Atlas[MongoDB Atlas<br/>3 Node Replica Set]
    App2 -->|TLS + SRV| Atlas
    Atlas -->| PITR | Backup[Automatic Backup<br/>7Day Reserved]

    style Atlas fill:#d4edda
    style App1 fill:#cce5ff

سياسة تكوين البيئة:

متغير بيئي القيمة في بيئة التطوير القيمة في بيئة الإنتاج طريقة الإدارة
NODE_ENV التطوير الإنتاج .env / إعدادات النظام الأساسي
MONGODB_URI mongodb://localhost:27017 mongodb+srv://... إدارة مفاتيح النظام الأساسي
JWT_SECRET test-secret سلسلة عشوائية من 256 بت openssl rand -hex 32
CORS_ORIGIN * https://shophub.example.com .env / إعدادات النظام الأساسي
الميناء 3000 تخصيص الرصيف الإعداد الافتراضي مناسب

تصميم فحص الحالة: لا تقتصر وظيفة نقطة النهاية /healthz على التحقق من تشغيل Express فحسب، بل تتأكد أيضًا من إمكانية الوصول إلى MongoDB — ففي حالة انقطاع الاتصال بقاعدة البيانات، يجب أن يُرجع التطبيق رمز الحالة 503 (الخدمة غير متاحة) بدلاً من 200، حتى يتمكن موزع الحمل من إزالة المثيلات غير السليمة تلقائيًا.

(1) تكوين MongoDB Atlas

BASH
# 1. Create Atlas Cluster(Recommended Courses #01)
# 2. Layout IP Whitelist:0.0.0.0/0(Development)or application server IP
# 3. Create a Database User:app_user / <password>
# 4. Get the connection string:
mongodb+srv://app_user:<password>@cluster0.mongodb.net/shophub?retryWrites=true&w=majority

(2) متغيرات البيئة (الإنتاج)

BASH
# .env.production
NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb+srv://app_user:StrongPass@cluster0.mongodb.net/shophub?retryWrites=true&w=majority
JWT_SECRET=<generated-strong-secret-256-bit>
JWT_EXPIRES_IN=7d
CORS_ORIGIN=https://shophub.example.com

(3) الفحص الطبي

JAVASCRIPT
app.get('/healthz', async (req, res) => {
  try {
    const db = mongoose.connection.db;
    await db.admin().command({ ping: 1 });
    res.json({
      status: 'ok',
      uptime: process.uptime(),
      mongo: 'connected',
      timestamp: new Date().toISOString()
    });
  } catch (err) {
    res.status(503).json({ status: 'error', error: err.message });
  }
});

(4) التنفيذ / نشر السكك الحديدية

BASH
# === Render Deployment ===
# 1. Connect GitHub Warehouse
# 2. Set Environment Variables (MONGODB_URI, JWT_SECRET, etc.)
# 3. Set Up Build Commands:npm install
# 4. Set the startup command:npm start
# 5. Automatic HTTPS + Deployment

# === Railway Deployment ===
railway login
railway init
railway add mongodb  # Add with One Click MongoDB
railway up

(5) قائمة مراجعة لتحسين الأداء

JAVASCRIPT
// === src/app.js Optimized Version ===
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URI, {
  maxPoolSize: 50,
  minPoolSize: 5,
  serverSelectionTimeoutMS: 5000
});

app.use(express.json({ limit: '1mb' }));
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*',
  credentials: true
}));


8. عرض توضيحي شامل للمشروع

عملية بدء المشروع والاختبار: يتبع العرض التوضيحي الكامل للمشروع العملية التالية: «إعداد البيئة → بدء تشغيل الخدمات → اختبار واجهات برمجة التطبيقات (APIs) → التحقق من الأداء الوظيفي → النشر والتشغيل». أولاً، قم بتشغيل مجموعة النسخ المتماثلة لـ MongoDB (تتطلب المعاملات و«تدفقات التغيير» وجود مجموعة نسخ متماثلة)، ثم قم بتشغيل تطبيق Express، وأخيرًا استخدم curl لاختبار نقاط نهاية واجهة برمجة التطبيقات (API) الأساسية.

قائمة مراجعة اختبار واجهة برمجة التطبيقات (API):

# الميزة نقطة النهاية رمز الحالة المتوقع
1 التسجيل POST /api/auth/register 201
2 تسجيل الدخول POST /api/auth/login 200
3 إنشاء منتج POST /api/products 201 (المسؤول)
4 قائمة المنتجات GET /api/products 200
5 نشر تقييم POST /api/products/:id/reviews 201
6 قائمة التقييمات GET /api/products/:id/reviews 200
7 إعجاب POST /api/reviews/:id/like 200
8 إحصائيات المنتج GET /api/products/:id/stats 200
9 فحص الحالة GET /healthz 200
BASH
# === Launch the Project ===
npm install
npm start

# === API Usage Example ===

# 1. Registered Users
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","username":"alice","password":"Pass123!"}'

# 2. Log In
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"Pass123!"}'

# 3. Create a Review
curl -X POST http://localhost:3000/api/products/<productId>/reviews \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"content":"Great product!","rating":5}'

# 4. View Product Statistics
curl http://localhost:3000/api/products/<productId>/stats

# 5. Health Checkup
curl http://localhost:3000/healthz

▶ المثال 1: عرض توضيحي للتعليقات المتداخلة + ميزة «الإعجاب»

JAVASCRIPT
// === Scenario: Alice reviews a product, Bob replies to Alice, Charlie likes it ===

// 1. Alice Published 5 Star Reviews
const aliceReview = await Review.create({
  productId: '647f1f77bcf86cd799439011',
  userId: aliceId,
  content: 'Excellent smartphone! The camera quality is outstanding.',
  rating: 5,
  parentId: null  // Top Comments
});
// Back: { _id: 'review001', content: '...', rating: 5, likeCount: 0 }

// 2. Bob Reply Alice
const bobReply = await Review.create({
  productId: '647f1f77bcf86cd799439011',
  userId: bobId,
  content: 'I agree, the camera is amazing!',
  rating: 5,
  parentId: aliceReview._id  // Quote parent's comment
});
// Back: { _id: 'review002', parentId: 'review001', content: '...' }

// 3. Charlie likes Alice's comment
await Review.updateOne(
  { _id: aliceReview._id },
  { $addToSet: { likes: charlieId }, $inc: { likeCount: 1 } }
);
// Alice Comments on: { likeCount: 1, likes: [charlieId] }

// 4. Like again → Cancel
await Review.updateOne(
  { _id: aliceReview._id },
  { $pull: { likes: charlieId }, $inc: { likeCount: -1 } }
);
// Alice Comments on: { likeCount: 0, likes: [] }

// 5. Querying a Nested Comment Tree
const reviews = await Review.find({ productId: '647f...', parentId: null })
  .populate('userId', 'username avatar')
  .lean();
const replies = await Review.find({ parentId: { $in: reviews.map(r => r._id) } })
  .populate('userId', 'username avatar')
  .lean();
const tree = reviews.map(r => ({
  ...r,
  replies: replies.filter(rep => rep.parentId.toString() === r._id.toString())
}));

console.log(JSON.stringify(tree, null, 2));
// [{
//   content: 'Excellent smartphone!...',
//   userId: { username: 'alice', avatar: '...' },
//   replies: [{ content: 'I agree...', userId: { username: 'bob' } }]
// }]

الناتج: بنية شجرية متداخلة للتعليقات: تعليقات أليس → ردود بوب، إعجاب/عدم إعجاب تشارلي.

▶ المثال 2: عرض توضيحي مباشر لنظام «ShopHub Review» الكامل

BASH
# === 1. Start MongoDB Dungeon Collection ===
docker run -d --name mongo -p 27017:27017 mongo:7.0 --replSet rs0
docker exec mongo mongosh --eval 'rs.initiate()'

# === 2. Start Node.js Applications ===
npm install
npm start

# Output:
# ✅ MongoDB connected: localhost
# 🚀 Server running on port 3000

# === 3. Test Core API ===

# 3.1 User Registration
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","username":"alice","password":"Pass123!"}'

# Back:
# {
#   "success": true,
#   "user": { "id": "...", "email": "alice@example.com", "username": "alice" },
#   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# }

# 3.2 User Login
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"Pass123!"}'

# 3.3 Create a Product
curl -X POST http://localhost:3000/api/products \
  -H "Authorization: Bearer <admin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "PHONE-001",
    "title": "Smartphone X",
    "price": 599,
    "category": "Electronics",
    "stock": 50
  }'

# 3.4 Product List(With pagination + Projection)
curl 'http://localhost:3000/api/products?page=1&limit=20&category=Electronics'

# Back:
# {
#   "success": true,
#   "data": [{ "sku": "PHONE-001", "title": "Smartphone X", "price": 599, "thumbnail": "...", "rating": 0 }],
#   "meta": { "page": 1, "limit": 20, "total": 1, "pages": 1 }
# }

# 3.5 Add a comment
curl -X POST http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews \
  -H "Authorization: Bearer <user_token>" \
  -H "Content-Type: application/json" \
  -d '{"content":"Great phone!","rating":5}'

# 3.6 Like and Comment
curl -X POST http://localhost:3000/api/reviews/507f1f77bcf86cd799439031/like \
  -H "Authorization: Bearer <user_token>"

# 3.7 View Product Statistics
curl http://localhost:3000/api/products/507f1f77bcf86cd799439021/stats

# Back:
# {
#   "total": [{ "count": 1 }],
#   "avgRating": [{ "avg": 5 }],
#   "ratingDistribution": [{ "_id": 5, "count": 1 }],
#   "recentReviews": [{ "content": "Great phone!", "rating": 5, "username": "alice" }]
# }

# 3.8 Health Checkup
curl http://localhost:3000/healthz

# Back:
# {
#   "status": "ok",
#   "uptime": 1234,
#   "mongo": "connected",
#   "timestamp": "2026-07-06T10:00:00.000Z"
# }

# === 4. Deploy to Render/Railway ===
git push heroku main
# Automatic Deployment,Environment Variables MONGODB_URI Orientation Atlas

# === 5. Performance Monitoring(Production)===
# Datadog APM Automatic Tracking:
# - mongoose Query Duration
# - API Response Time
# - Number of database connections
# - Slow Query Alerts

الناتج: نظام تقييمات متكامل للتجارة الإلكترونية يوضح سير العمل بأكمله — بدءًا من التسجيل، وتسجيل الدخول، وإدارة المنتجات، وعمليات CRUD الخاصة بالتقييمات، والإعجاب، والإحصاءات، وصولاً إلى فحوصات الحالة — ويمكن نشره مباشرةً في بيئة الإنتاج.

▶ مثال 3: واجهة برمجة تطبيقات إحصاءات التقييمات المتقدمة(الصعوبة ⭐⭐)

JAVASCRIPT
// واجهة برمجة تطبيقات إحصاءات التقييمات لـ ShopHub
const express = require('express');
const mongoose = require('mongoose');
const router = express.Router();
const Review = require('../models/Review');
const Product = require('../models/Product');

// إحصاءات التقييمات الشاملة
router.get('/products/:productId/stats', async (req, res) => {
  const { productId } = req.params;

  const stats = await Review.aggregate([
    {
      $match: {
        productId: new mongoose.Types.ObjectId(productId),
        parentId: null,
        isDeleted: { $ne: true }
      }
    },
    {
      $facet: {
        // الإجمالي والمتوسط
        overview: [
          {
            $group: {
              _id: null,
              totalReviews: { $sum: 1 },
              avgRating: { $avg: '$rating' },
              totalLikes: { $sum: '$likeCount' }
            }
          }
        ],
        // توزيع التقييمات (1-5 نجوم)
        ratingDistribution: [
          {
            $group: {
              _id: '$rating',
              count: { $sum: 1 }
            }
          },
          { $sort: { _id: 1 } }
        ],
        // أكثر التقييمات فائدة
        topHelpful: [
          { $sort: { likeCount: -1 } },
          { $limit: 5 },
          {
            $lookup: {
              from: 'users',
              localField: 'userId',
              foreignField: '_id',
              as: 'user'
            }
          },
          { $unwind: '$user' },
          {
            $project: {
              content: 1,
              rating: 1,
              likeCount: 1,
              createdAt: 1,
              username: '$user.username'
            }
          }
        ],
        // التقييمات الحديثة
        recentReviews: [
          { $sort: { createdAt: -1 } },
          { $limit: 5 },
          {
            $lookup: {
              from: 'users',
              localField: 'userId',
              foreignField: '_id',
              as: 'user'
            }
          },
          { $unwind: '$user' },
          {
            $project: {
              content: 1,
              rating: 1,
              createdAt: 1,
              username: '$user.username'
            }
          }
        ]
      }
    }
  ]);

  res.json({
    success: true,
    data: {
      overview: stats[0].overview[0] || { totalReviews: 0, avgRating: 0, totalLikes: 0 },
      ratingDistribution: stats[0].ratingDistribution,
      topHelpful: stats[0].topHelpful,
      recentReviews: stats[0].recentReviews
    }
  });
});

module.exports = router;

// اختبار API:
// GET /api/products/507f1f77bcf86cd799439021/stats

الإخراج:

JSON
{
  "success": true,
  "data": {
    "overview": { "totalReviews": 128, "avgRating": 4.3, "totalLikes": 892 },
    "ratingDistribution": [
      { "_id": 1, "count": 5 },
      { "_id": 2, "count": 8 },
      { "_id": 3, "count": 15 },
      { "_id": 4, "count": 35 },
      { "_id": 5, "count": 65 }
    ],
    "topHelpful": [
      { "content": "منتج ممتاز!", "rating": 5, "likeCount": 45, "username": "alice" }
    ],
    "recentReviews": [
      { "content": "جودة عالية", "rating": 4, "createdAt": "2026-07-20T09:30:00Z", "username": "bob" }
    ]
  }
}

❓ أسئلة شائعة

س كيف يمكننا تحسين المشروع بشكل أكبر بعد اكتماله؟
ج تخزين المنتجات الأكثر رواجًا مؤقتًا في Redis، والبحث عن النص الكامل باستخدام Elasticsearch، واستخدام شبكة توزيع المحتوى (CDN) للموارد الثابتة، والنشر باستخدام الحاويات مع K8s.
س ما هي الإجراءات المتبعة لمكافحة البريد العشوائي في نظام التعليقات؟
ج حدود تكرار التعليقات، وتصفية الكلمات الحساسة، ونظام الإبلاغ عن المستخدمين، ونظام الإشراف اليدوي في الخلفية.
س كيف يمكنني إعداد إشعارات التعليقات؟
ج استخدم ميزة «تغيير التدفقات» لمراقبة التغييرات التي تطرأ على التعليقات وتفعيل إشعارات البريد الإلكتروني أو الإشعارات الفورية.

📖 ملخص


📝 تمارين

  1. السؤال الأساسي (⭐): قم بإعداد الهيكل الأساسي للمشروع (بما في ذلك ملفات package.json و.env وapp.js).
  2. المشاكل الأساسية (⭐): قم بتنفيذ النماذج الثلاثة: «المستخدم»، و«المنتج»، و«التقييم».
  3. تمرين متقدم (⭐⭐): تنفيذ عمليات CRUD الخاصة بالتعليقات (إنشاء، عرض قائمة، الإعجاب، الحذف المؤقت).
  4. تمرين متقدم (⭐⭐): تنفيذ الإحصاءات التجميعية (توزيع التقييمات + إحصاءات المنتج + قوائم الأكثر رواجًا).
  5. التحدي (⭐⭐⭐): نشر نظام كامل لمراجعة التجارة الإلكترونية على منصة Atlas أو Render، بما في ذلك جميع ميزات الوحدات الست.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%