مناف حوراني
كلّ الأعمال

001

2026

Megastore

منصّة تجارة إلكترونية ثلاثية اللغة ومتعدّدة العملات: خمسة تطبيقات خلف طبقة واحدة من العقود المشتركة، إضافةً إلى تطبيق Flutter.

تطبيقات في المستودع الموحّد
5
حزم مشتركة
6
لغات، العربية أولاً
3
عملات
5

البنية

بنية منصّة Megastoreتصل المتصفّحات وتطبيق Flutter إلى متجر Next.js ولوحة التحكّم، وكلاهما يستدعي واجهة NestJS واحدة. والواجهة البرمجية هي الخدمة الوحيدة التي تلمس PostgreSQL. كما تقرأ وتكتب في Redis للتخزين المؤقّت وتحديد المعدّل، وتضع المهامّ في طوابير BullMQ لعملية عامل منفصلة، وتخزّن الوسائط المعالَجة في MinIO، وتستدعي خدمة Python داخلية لإزالة الخلفيات. أمّا العامل فيتولّى تحويل الصور والفيديو وتجميع الإحصاءات المجدولة والإشعارات.CLIENTSAPPSSERVICESDATAenqueueBrowserwebFlutter appiOS · AndroidStorefrontNext.js 16DashboardNext.js 16NestJS APIsole DB ownerWorkerBullMQPostgreSQLPrisma 7Rediscache · queue · ratesMinIOprocessed mediamedia-pyFastAPI · rembg
تصل المتصفّحات وتطبيق Flutter إلى متجر Next.js ولوحة التحكّم، وكلاهما يستدعي واجهة NestJS واحدة. والواجهة البرمجية هي الخدمة الوحيدة التي تلمس PostgreSQL. كما تقرأ وتكتب في Redis للتخزين المؤقّت وتحديد المعدّل، وتضع المهامّ في طوابير BullMQ لعملية عامل منفصلة، وتخزّن الوسائط المعالَجة في MinIO، وتستدعي خدمة Python داخلية لإزالة الخلفيات. أمّا العامل فيتولّى تحويل الصور والفيديو وتجميع الإحصاءات المجدولة والإشعارات.
  • عميل
  • خدمة
  • مخزن بيانات
  • خارجي

المشكلة

‏Megastore هي الوكيل الرسمي لعلامتَي TELETAB وOUKITEL في سوريا. احتاجت إلى منصّة تجارة حقيقية — لا قالباً جاهزاً — تخدم العملاء بالإنجليزية والعربية والتركية، وتسعّر بخمس عملات، وتغذّي تطبيق Flutter من الواجهة البرمجية نفسها التي تخدم المتجر على الويب.

متجر عامّ يشمل الإلكترونيات والملابس ومستحضرات التجميل والعناية الصحية، يبيع لثلاثة أسواق لغوية بخمس عملات. والعربية تُكتب من اليمين إلى اليسار، ولا يمكن معالجتها كإضافة لاحقة في طبقة CSS. كما تختلف المنتجات بحسب خصائصها، فالشيء الذي يشتريه العميل فعلياً هو المتغيّر (variant) لا المنتج. وينبغي للكتالوج نفسه أن يخدم متجراً على الويب ولوحة تحكّم وتطبيق هاتف من دون ثلاثة تعريفات متباعدة لما يعنيه «المنتج».

المقاربة

كتبتُ البنية المعمارية قبل كتابة أيّ شيفرة — وثيقة مواصفات بقرارات مرقّمة، يحمل كلّ قرار مبرّره والبدائل التي رفضتها. ثمّ بنيتُ وفقها: مستودع أحادي بـ Turborepo، وواجهة برمجية بـ NestJS هي المالك الوحيد لقاعدة البيانات، وتطبيقا Next.js، وعامل خلفي بـ BullMQ، وخدمة Python صغيرة لإزالة الخلفيات. وتقع مخطّطات Zod المشتركة في حزمة واحدة يستوردها كلّ تطبيق، بحيث يكون مدقّق الواجهة البرمجية ومحلّل نموذج لوحة التحكّم هما الكائن نفسه حرفياً.

قرارات كان لها أثر

  1. 01

    مصدر واحد للحقيقة في الأنواع

    كلّ مخطّط مشترك هو كائن Zod في `packages/contracts`، تستهلكه NestJS عبر `nestjs-zod` ويستهلكه React Hook Form عبر `zodResolver`. خمسة تطبيقات، وتعريف واحد. ولا يمكن لحقل أن يتباعد بين الواجهة التي تدقّقه والنموذج الذي يرسله، لأنّه لا يوجد إلا حقل واحد.

  2. 02

    منظومتا مصادقة مستقلّتان

    لا يتشارك العملاء وموظّفو الإدارة أيّ جداول أو رموز أو حرّاس أو أزواج مفاتيح. لكلٍّ منهما مفتاح توقيع ES256 خاصّ به ونقطة JWKS خاصّة به. فرمز عميل مسروق لا يكون ناقص الصلاحية أمام مسار إداري فحسب — بل يفشل في التحقّق من التوقيع أصلاً. حدّ الصلاحيات هنا تشفيريّ لا شرطيّ.

  3. 03

    كلمات المرور لا تنتقل أبداً بنصّ صريح

    يغلّف المتصفّح كلمة المرور في مظروف AES-256-GCM، ويشفّر المفتاح المتماثل بمفتاح RSA-OAEP-256 عامّ يجلبه من الواجهة البرمجية، ثمّ يرسل المظروف. يبقى TLS إلزامياً — فهذا دفاع في العمق. وما يضيفه هو أنّ كلمة المرور لا يمكن أن تظهر في سجلّ وسيط أو أثر مراقبة أداء أو نسخة ملتقطة من الطلب. وتُدوَّر المفاتيح كلّ 24 ساعة في Redis، مع قبول المفتاح السابق 24 ساعة إضافية.

  4. 04

    عالِج قبل أن تخزّن

    لا يصل شيء غير معالَج إلى تخزين الكائنات. يُفحَص الملف المرفوع ببصمة البايتات الأولى لكشف نوع محتوى مزيّف، ثمّ يُقصّ، ويُدار تلقائياً وفق EXIF، وتُجرَّد بياناته الوصفية، ويُصغَّر، ويُعاد ترميزه إلى WebP — كلّ ذلك داخل الطلب نفسه. رفعٌ واحد ينتج كائناً نهائياً واحداً. لا حاويةَ انتظار ولا مهمّةَ تنظيف للملفّات اليتيمة، لأنّه لا وجود لملفّات يتيمة أساساً.

  5. 05

    المتغيّر هو المنتج

    ‏`Product` حاوية تجميع رقيقة، أمّا `ProductVariant` فيحمل السعر والعملة والمخزون والمعرّفات والوسائط والتصنيف. القوائم والمرشّحات والبحث والسلّة والطلبات تعمل كلّها على المتغيّرات. والمنتج البسيط هو ببساطة منتج بمتغيّر واحد — ما يعني أنّه لا يوجد أيّ تفرّع `isSimple` في الشيفرة. مسار واحد للتنفيذ في كلّ مكان.

  6. 06

    ‏Postgres أوّلاً، وMeilisearch لاحقاً

    أعمدة tsvector مولَّدة بفهارس GIN مع `pg_trgm` للمطابقة التقريبية تغطّي البحث والتصفية على نطاق الإطلاق من دون أيّ بنية تحتية إضافية. وMeilisearch موصّف كتحسين يمكن إدخاله لاحقاً من دون تغيير المخطّط. فإضافة حاوية لا تحتاجها بعد تكلفةٌ لا قدرة.

مسائل صعبة

  1. 01

    تصفية السعر عبر خمس عملات أساسية

    كلّ متغيّر مسعَّر بعملته الأساسية، لكنّ العميل الذي يصفّي حسب السعر يفكّر بعملة عرض واحدة. والتحويل داخل التطبيق يعني تحميل كلّ المتغيّرات قبل التصفية — وهو غير عمليّ. لذا فالمرشّح عبارة عن CTE في SQL يضمّ جدول أسعار الصرف ويقارن `price × rate` مقابل الحدود داخل الاستعلام نفسه، فتبقى التصفية في قاعدة البيانات ويبقى الترقيم صحيحاً. والتحويل نفسه يقود الترتيب حسب السعر.

  2. 02

    جعل كلمة المرور المشفَّرة غير قابلة لإعادة الإرسال

    لا يكفي تشفير كلمة المرور وحده — إذ يمكن ببساطة إعادة إرسال مظروف معترَض. لذا يحمل المحتوى المشفَّر طابعاً زمنياً ورقماً عشوائياً بطول 128 بت. يرفض الخادم أيّ شيء خارج نافذة 120 ثانية، ثمّ يحجز الرقم العشوائي بـ `SETNX` في Redis لمدّة خمس دقائق. فالمظروف المُعاد يفشل في المحاولة الثانية أيّاً كانت طريقة الحصول عليه.

  3. 03

    فيديو للمنتجات من دون منصّة فيديو

    احتاجت فيديوهات المنتجات تشغيلاً تكيّفياً، لكنّ منصّات الفيديو المستضافة ذاتياً مصمَّمة للبثّ المباشر وثقيلة. لذا يفحص العامل الملف بـ ffprobe، ويبني فقط الدرجات التي يحتملها المصدر — 1080p و720p و480p — بإطارات مفتاحية متوائمة كي ينتقل المشغّل بينها بسلاسة، ثمّ يكتب قائمة تشغيل HLS رئيسية. وإلى أن يكتمل السلّم يُشغَّل الملف الأصلي تدريجياً، فلا شيء يتوقّف بانتظار التحويل.

  4. 04

    العربية لغةً من الدرجة الأولى

    الاتجاه من اليمين إلى اليسار مسألة تخطيط لا مسألة ترجمة. يستخدم التخطيط الخصائص المنطقية في كلّ مكان بدل يمين/يسار، فينقلب الاتجاه من دون ورقة أنماط موازية. وتحمل حقول قاعدة البيانات المترجَمة اللغات الثلاث بصيغة JSON، وللعربية معرّف مسار خاصّ بها — بينما تعيد التركية استخدام المعرّف الإنجليزي عمداً، وهو عُرف توجيهٍ يوفّر عموداً وصنفاً كاملاً من الأخطاء.

دوري

المهندس الوحيد. البنية المعمارية، وتصميم قاعدة البيانات، والواجهة البرمجية، والواجهتان الأماميتان، والعامل الخلفي، وخطّ معالجة الوسائط، وعميل Flutter، وبنية Docker التي يعمل عليها كلّ ذلك.

الحالة

قيد التطوير النشط. النسخة الأولى تطبيق من 424 ملفاً يضمّ سطح الإدارة كاملاً — الكتالوج والطلبات والمرتجعات ومكتبة الوسائط وتقارير التحليلات ومنشئ صفحة رئيسية بالسحب والإفلات. أمّا مساحة عمل النسخة الثانية فتعيد بناءه وفق البنية الموصوفة هنا. ونسخة iOS قيد المراجعة في App Store.

التقنيات

الخلفية
NestJS 11Prisma 7PostgreSQL 16RedisBullMQZod
الواجهة
Next.js 16React 19next-intlTailwind v4shadcn/ui
الوسائط
SharpFFmpeg / HLSMinIOFastAPIrembg
الهاتف
FlutterBloc / Cubitgo_routerget_itdio
البنية التحتية
DockerTurborepopnpmInfisicalTraefikCloudflareUmami