Files
tripz-llc/docs/30-siro-port-plan.md
T

18 KiB

30 — نقل تطبيقات سيرو (GetX) لتحل محل واجهات Tripz + دراسة دوكرة PHP سيرو

قرار المالك 2026-07-21. الباك إند NestJS مجمّد كما هو — كل التكيّف يحدث في طبقة استقبال البيانات داخل Flutter. المنهج: توثيق المجموعات هنا أولاً، ثم تنفيذ مجموعة واحدة في كل مرة مع برهان على جهاز حقيقي.


0) القرار وتقييمه الصادق

القرار: نسخ siro_rider وsiro_driver (GetX، مجرّبان ميدانياً 90–95%) إلى apps/rider وapps/driver ليحلا محل إعادة البناء Cubit/Bloc، مع تحويل طبقة البيانات فقط إلى عقد باك إند Tripz.

ملاحظة صدق تقني (حتى لا نبني القرار على سبب خاطئ): البطء الملحوظ على الجهاز عبر كابل USB سببه الغالب وضع debug نفسه (JIT + asserts + logging + الكابل)، وليس Bloc/Cubit كإطار — في build release الفرق بين الإطارين هامشي. القرار سليم لكن لسببه الحقيقي:

الحجة الحقيقية الدليل المتحقق
تطبيقات سيرو مكتملة ومجرّبة على مستخدمين حقيقيين rider = 234 ملف dart، driver = 281 ملف
إعادة البناء ما زال أمامها ذيل UX طويل (مرحلة Q كلها) rider الجديد = 77 ملف، driver = 53 ملف
المالك يعرف كود سيرو شبراً شبراً → سرعة تطوير أعلى خبرة مباشرة
طبقة الشبكة في سيرو مركزية فعلاً → التحويل ممكن بدون مسّ الواجهات crud.dart (625 سطر) يستورده 46 ملفاً في rider و71 في driver؛ الروابط كلها في links.dart (547 سطر)

ما لا يتغير: الباك إند (A–K + ما بعدها) · قرار الخرائط المباشرة (docs/25 + ذاكرة maps-direct) · قرار الهويتين المنفصلتين راكب/سائق (commit 9f7bc31) · نموذج المال (محفظتان) · مبدأ Redis-first.


1) الحقائق المتحقق منها (فُحصت من الكود 2026-07-21)

تطبيقا سيرو

  • الحالة: GetX + get_storage، شبكة http + dio، خرائط intaleq_maps ^2.2.1 (نفس ما يستخدمه Tripz — صفر عمل هجرة خرائط).
  • crud.dart يحوي أصلاً: فحص صلاحية JWT محلياً + تجديد تلقائي + SSL pinning + NetGuard + إبلاغ أخطاء مركزي. البنية الذهنية مطابقة لما يحتاجه عقد Tripz — التغيير في التفاصيل لا في الفلسفة.
  • links.dart يوزّع النداءات على أربع عائلات سيرفرات لكل دولة (API رئيسي · paymentServer · location API · locationServerSide سوكيت · routesOsm). في Tripz تنهار كلها إلى اثنين: tripz-api.intaleqapp.com + map-saas.intaleqapp.com مباشرة.
  • سوكيت: socket_io_client 1.0.2 — بروتوكول Socket.IO v2 قديم.
  • دفع: التطبيق ينادي PayMob مباشرة من Flutter (controller/payment/paymob*.dart).
  • هويات سيرو: com.siro.rider / com.siro.siro_driver + shorebird خاصة بسيرو (44245793…, f8e9c087…) — لا تُستخدم في Tripz.
  • حزم محلية: rider يحمل fork get/get_storage داخله (./packages/) — مكتفٍ ذاتياً. driver يشير خارج المستودع (../../Intaleq/packages/get) — يجب ضمّها داخله عند النسخ.

باك إند Tripz (الطرف الثابت)

  • 38 controller تغطي كل الدورة (auth·trips·dispatch·wallet·credit·payments·payouts·ratings·chat·notifications·documents·vehicles·rewards·geofence·transit…).
  • realtime = NestJS socket.io v4 + redis-adapter، بلا allowEIO3.
  • المرجع الحي لعقد النداءات: backend/scripts/e2e-test.mjs — يمرّ على الدورة كاملة (دخول→سائق→رحلة→حالات→WS→دفع→عمولة→تقييم→payout) بالأشكال الفعلية للطلب والرد. هذا هو "قاموس الترجمة" عند تحويل كل وحدة.

2) فجوات العقد الست (كل واحدة مصيدة معروفة سلفاً)

  1. المغلف والترويسات: ردود PHP بأشكالها الحرة ↔ ردود NestJS DTO. الترويسات الجديدة إلزامية: Authorization JWT + x-tenant-id (slug) + x-app-role (قرار الهويتين) + لاحقاً x-device-id وتوقيع الطلبات. المعرّفات UUID لا int، والمال عشري num (1.10 JOD تكسر أي int).
  2. انهيار عائلات السيرفرات: كل switch (currentCountry) في links.dart يُستبدل: الباك إند واحد متعدد المستأجرين (الدولة تُحل من tenant.countryPack على السيرفر)، والخرائط/المسارات مباشرة لـ map-saas بترويسة x-api-key (العقد المثبت: reverse يرجع مصفوفة، route في حقل points، لا lang).
  3. السوكيت غير متوافق أصلاً: عميل v2 لا يتصل بخادم v4 — ترقية socket_io_client إلى ^2/^3 إلزامية + إعادة رسم أسماء الأحداث + قناة رفع مواقع السائق تتحول من driver_socket.php (بورت 2020) إلى realtime gateway الموحّد.
  4. الدفع يعود للسيرفر: نداءات PayMob المباشرة من Flutter تُعاد عبر payments.controller (بوابة + سجل محولات) — المفاتيح لا تسكن التطبيق.
  5. الهويات التجارية: تُستبدل هويات سيرو بهويات Tripz المنشورة (rider com.mobileapp.store.ride / shorebird 496cb3ac…). ⚠️ هوية driver متعارضة بين المنصتين (com.sefer_driver أندرويد / com.sefer.driver iOS) — قرار المالك مطلوب قبل أي إصدار driver. Firebase وshorebird والإضافات الأصلية تؤخذ من مستودع Tripz الحالي، لا من سيرو.
  6. حزم fork المحلية: تُضم get/get_storage داخل apps/driver/packages/ أسوة بـ rider، حتى يبقى المستودع مكتفياً ذاتياً.

3) خطة التنفيذ — وحدة وحدة (G0…G7)

المنهج الثابت لكل مجموعة: (أ) التقاط نداء سيرو الحالي (link + payload + شكل الرد) → (ب) مطابقته بعقد Tripz من e2e-test.mjs وswagger → (ج) التعديل في طبقة البيانات فقط (crud/links/models) وترك الواجهات ومنطق الـ controllers كما هو ما أمكن → (د) برهان على جهاز حقيقي قبل الانتقال للتالية. البناء والاختبار على السيرفر لا على الماك (القاعدة الثابتة).

# المجموعة المحتوى البرهان
G0 الاستيراد والترجمة أرشفة الكود الحالي بفرع archive/cubit-rebuild (لا حذف — فيه إصلاحات تُحمل، §4) · نسخ تطبيقي سيرو إلى apps/ · تبديل الهويات (bundle/firebase/shorebird من Tripz) · ضم fork الحزم · ترقية socket_io_client · حذف Twilio وبقايا لا تلزم flutter analyze صفر أخطاء + الإقلاع لشاشة الدخول على الجهاز
G1 المصادقة قلب crud.dart + links.dart للعقد الجديد (المغلف، الترويسات الأربع، تجديد refresh token) · دخول هاتف+OTP دخول حقيقي بـ OTP على الجهاز، والتوكن يتجدد
G2 الهوم والخريطة map-saas مباشر (العقد المثبت §2.2) · السائقون القريبون من الباك إند خريطة + عنوان عكسي + سيارات قريبة تظهر
G3 دورة الرحلة (راكب) تسعيرة → طلب → مطابقة → حالات الرحلة → أحداث WS v4 رحلة كاملة على جهازين حتى completed (ملاحظة: R1 sweeper ناقص على الباك — حالة البحث قد لا تنتهي من السيرفر؛ يُختبر بوجود سائق)
G4 تطبيق السائق دخول بـ x-app-role: driver · online/offline · رفع المواقع إلى gateway الموحّد · استقبال العروض · حالات الرحلة نفس رحلة G3 من طرف السائق، والموقع يظهر عند الراكب
G5 المال محفظة الراكب · رصيد السائق والعمولة · شحن عبر الباك إند (PayMob) · payout بـ OTP شحن تجريبي + خصم عمولة رحلة + طلب payout
G6 الإشعارات والتقييم والدردشة FCM (⚠️ العقد الصحيح POST /notifications/token مع platform — مسار /devices/fcm-token كان 404 صامتاً) · التقييم الإجباري GET /trips/rating/pending · الدردشة إشعار يصل والتطبيق مغلق + تقييم إجباري يظهر
G7 التصليب والتنظيف SSL pinning على شهادة tripz-api · ثم تفعيل PAYMENTS_REQUIRE_SIGNATURE وAUTH_REQUIRE_DEVICE_BINDING على السيرفر · حذف الكود الميت (PHP-era) · مواءمة مع خطة الفئات lite/pro/max (docs/22 §1.5) e2e يمر والفلاغان مفعّلان

4) ما يُحمل من إعادة البناء الحالية قبل الأرشفة (لا يعاد اكتشافه)

  • عقد map-saas المثبت بالتجربة: x-api-key ترويسة (query مرفوض 400) · reverse مصفوفة مرتبة بالأقرب (name/name_ar/address) · route في points — موجود في apps/rider/lib/core/api/antlaq_api.dart وfeatures/home/data/maps_repository.dart.
  • درس FCM: القناة كانت ميتة بسبب اختلاف المسار — العقد الصحيح موثق في G6.
  • قرار الهويتين ومفتاح MAP_API_KEY عبر --dart-define (المفتاح الحالي مفتاح سيرو؛ مفتاح Tripz مقيّد بالبصمة يصدر لاحقاً).

5) دراسة دوكرة سيرو PHP (مسار موازٍ مستقل عن النقل)

5.1 الجرد (فُحص 2026-07-21 — لا يوجد أي Docker حالياً في مستودع سيرو)

backend/ (API الرئيسي) · payment_server/v2 · loction_server/ (Workerman + PHPSocketIO — driver_socket.php بورت 2020 WS + 2021 HTTP داخلي، Redis-only مبطّن كل 500ms) · passenger_server/ · ride_server/ · siro_admin/ · siromove.com/ + MySQL + Redis.

5.2 الشكل المقترح — compose واحدة لكل نسخة

nginx (موجّه لكل الخدمات) ─┬─ php-fpm  ← backend + passenger + ride + payment + admin (vhosts)
                           ├─ php-cli  ← driver_socket.php (Workerman يدير عمّاله بنفسه، restart: always)
                           ├─ mysql    (volume دائم + نسخ احتياطي)
                           └─ redis
.env واحدة لكل عميل: الدومين · مفاتيح OTP/دفع · كلمات سر القواعد · مفتاح map-saas
  • استنساخ عميل جديد = git clone + .env + docker compose up -d + استيراد السكيمات الثلاث (schema_primary/ride/tracking + WalletDB + locationDB) — هدف واقعي: نسخة عاملة في يوم، وتسليم عميل في أسبوع (الوقت الباقي للعلامة والتطبيقات والمتاجر).
  • كل عميل على سيرفره الخاص = عزل كامل بالتعريف (لا حاجة لهندسة multi-tenant في PHP).

5.3 السعة: هدف 10,000 رحلة في الذروة

حساب مبدئي: 10k رحلة/ساعة ≈ 2.8 رحلة/ثانية ≈ 60–120 rps على API (بمعدل 20–40 نداء للرحلة) + سوكيت المواقع (Redis-only ومبطّن أصلاً — ليس عنق الزجاجة). عنق الزجاجة المتوقع: MySQL وحوض php-fpm. سيرفر 4–8 vCPU يفترض أن يكفي. لكن القاعدة قاعدة: برهان لا تقدير. مجلد stress_test/ موجود في مستودع سيرو — يُشغَّل على نسخة docker مستنسخة نظيفة، ويُلتزم فقط بالرقم الذي يثبته الاختبار. (للمقارنة: باك إند Tripz أثبت ~1.15M رحلة/يوم على السيرفر المشترك.)

5.4 🔴 شرط غير قابل للتفاوض قبل أول عميل: طقم إصلاح ما قبل الاستنساخ

استنساخ الكود = استنساخ الثغرات. ثغرة payout في نسخة عميل = سرقة مال حقيقية باسمنا. من docs/21-siro-audit.md (17 عيباً)، عيوب P0 التي تُصلح مرة واحدة في الأصل قبل أي نسخة:

  1. IDOR في request_payout.php (driverId من الطلب لا من JWT) — سحب رصيد الغير.
  2. لا حجز رصيد عند طلب payout — طلبات متزامنة = صرف مزدوج.
  3. تناقض رسم 3500 (يُشترط جمعاً ويُخصم طرحاً، مكتوب مرتين) — تسريب مال.
  4. finalizePayout خمس كتابات بلا معاملة — فشل منتصفي يترك payout نصف مصفّى.
  5. driverWallet.amount نوع varchar(10) — مال كنص وSUM() على نصوص.

5.5 قرارات الأداء المحسومة (سؤال المالك 2026-07-21)

  • حاويات مقابل PHP عارٍ: على لينكس الحاوية = عمليات عادية بنفس النواة (namespaces/cgroups، لا محاكاة) — حمل CPU/ذاكرة ≈ صفر، والفرق الكلي 0–3% إذا وُضعت بيانات MySQL على volume مسمّى (يتجاوز overlayfs) وفُعّل opcache. القرار: دوكرة، بلا تردد — الأداء متكافئ وفائدة الاستنساخ حاسمة.
  • حاوية واحدة مقابل تقسيم: الأداء متطابق (نفس العمليات بالحالتين) — الفرق تشغيلي بحت لصالح التقسيم (إعادة تشغيل خدمة وحدها، حدود موارد، لوغات منفصلة). حاوية-واحدة-فيها-كل-شيء anti-pattern. القرار: التقسيم بالدور — 6 حاويات: nginx / php-fpm واحدة لكل الخدمات الطلبية / socket_driver (2020) / socket_passenger (3030) / mysql / redis.
  • JS مقابل PHP: لكل اتصال، Node أخف (event loop دائم بلا إقلاع لكل طلب) من نموذج fpm (عملية لكل طلب). لكن عند 10–20 ألف رحلة/ساعة كلاهما بعيد عن الحد — عنق الزجاجة MySQL وضبط fpm لا اللغة. ومسار سيرو الساخن (المواقع) أصلاً على Workerman = نفس نموذج Node بالضبط. مرجع: باك إند Tripz (نموذج JS) قاس ~1.15M رحلة/يوم على الصندوق المشترك؛ رقم PHP يقيسه الاختبار.

5.6 حالة التنفيذ (2026-07-21)

بُني كل شيء في مستودع سيرو، جاهز للتشغيل على السيرفر (الماك للكتابة فقط):

  • Siro/docker/ — compose (6 خدمات بحدود ذاكرة لصندوق 6 أنوية/12GB) + صورتا PHP (fpm + cli مع ext-event للسوكيتات) + nginx نقطة-واحدة (/backend + /v2/main بنفس أشكال مسارات اللايف — التطبيق يبدّل الدومين فقط) + قوالب env + runbook كامل بالأوامر في docker/README.md.
  • Siro/stress_test/rate_test.js — جديد: معدل مستمر (رحلات/ساعة) بدل الدفعة، JWT داخل Node (أزال اختناق execSync للـ PHP لكل رحلة)، p50/p95/p99 لكل نقطة + زمن العرض + الرحلة كاملة، مع ramp وحكم نجاح/فشل (<1% فشل و p95<500ms).
  • قاعدة الصدق في القياس: سيناريو الاختبار أخف من الرحلة الحقيقية 3–5× → لتبنّي "10k حقيقية" يجب عبور 30k سيناريو. الفحص خارج الذروة، بجولات 5–10 دقائق، مرتين. المُلتزم به للعملاء = الرقم العابر فقط.
  • المؤجل بقرار المالك: كل مسار الإيجار/الاستئجار على باك إند Tripz — التركيز الحالي على دوكرة PHP وتجربتها بتطبيق سيرو الحالي (تغيير base URLs فقط عبر .env التطبيق وإعادة بناء واحدة).

5.7 التموضع الاستراتيجي (حتى لا نشغّل منتجين للأبد)

  • نسخ PHP للعملاء = المنتج النقدي المرحلي: سريع، مجرّب، سيرو نفسها هي الدعاية. تجميد وظيفي عليه — إصلاحات أمان فقط، لا ميزات جديدة على PHP.
  • Tripz NestJS = المنصة النهائية: كل نسخة عميل مرشحة للهجرة لاحقاً كمستأجر (نفس فكرة "سيرو أول مستأجر"). بهذا لا نصير أسرى صيانة أسطول PHP متباعد النسخ.
  • سيرو نفسها أول من يُعاد تشغيله بالـ compose (سيرفر واحد يتحمل الذروة) — فتكون هي البرهان الحي أمام العملاء.

6) أسئلة مفتوحة للمالك

  1. هوية driver المنشورة — com.sefer_driver أم com.sefer.driver؟ يلزم قبل G0 للسائق (الراكب لا ينتظرها).
  2. أي المسارين أولاً — النقل (G0) أم compose سيرو (5.2)؟ يمكن التوازي: النقل عمل Flutter محلي والدوكرة عمل سيرفر.
  3. تسمية فرع الأرشفة archive/cubit-rebuild — موافق؟