Commit Graph
83 Commits
Author SHA1 Message Date
Hamza-AyedandClaude Opus 4.8 900c78bb97 fix: seed starting tariffs on tenant provision, not for siro only
ثغرة كُشفت على السيرفر: `SeedService` يزرع التعرفة لـ«سيرو» وحده، فمستأجرا
`intaleq` و`yalla` وُلدا بلا تسعير وبقيا كذلك حتى شُغّل `tariffs.sql` يدوياً.

الأثر صامت لا ظاهر: بلا تعرفة فعّالة لا يُقفل سعر للراكب، ثم تأخذ
`settleFare` القيمة `quoted_fare ?? 0` — رحلة مجانية وعمولة صفر.

`provision()` صار يزرع كل فئات الخدمة بعملة البلد ومنطقته الزمنية. مستودع
التعرفة محقون مباشرةً لا عبر `TariffService`: تلك الوحدة تستورد وحدة
المستأجرين، فالاستيراد المتبادل دورة.

وسطر لوغ الزرع كان يطبع 9 دائماً (ثابتاً لا عدّاداً) فلا يميّز «زُرعت» من
«كانت موجودة» — وهو الفرق الوحيد الذي يُقرأ منه.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:45:26 +03:00
Hamza-AyedandClaude Opus 4.8 90c3f71430 test: add live pricing script across all service classes and countries
يطلب رحلة حقيقية لكل فئة خدمة (9) × 3 مسافات × 3 بلدان عبر الـAPI الحي،
فالمسافة والزمن يأتيان من محرّك المسارات (انطلق) لا من افتراض.

يتحقّق آلياً من أمرين كانا عطلين حقيقيين قبل المجموعة M:
- ولا طلب يرجع بلا سعر (التعرفة المفقودة كانت تنتج رحلة مجانية بعمولة صفر).
- الفان أغلى من الاقتصادي في كل بلد (الأرقام الخام السابقة عكست الترتيب).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:40:30 +03:00
Hamza-Ayed 92f1eeb48c feat: implement rewards module with referral/coupon systems, update tariff seeding, and add trip distance service for improved billing accuracy. 2026-07-19 00:20:37 +03:00
Hamza-Ayed 22d18f32fe chore: add standard international tariff seeder script 2026-07-18 23:31:55 +03:00
Hamza-Ayed 840d0e54fe sec: scope app review 1234 bypass exclusively to siro tenant 2026-07-18 22:57:12 +03:00
Hamza-Ayed 898136bc1b fix: add missing driver_arriving state transition in simulation 2026-07-18 22:14:33 +03:00
Hamza-Ayed be18050bf2 fix: remove send-otp from simulation to bypass rate limiter 2026-07-18 22:11:56 +03:00
Hamza-Ayed fe376f8aa6 refactor: rename trip dispatch socket event to trip offer and update corresponding listener to trip:update 2026-07-18 22:10:54 +03:00
Hamza-Ayed 7a0372b018 chore: add comprehensive error logging to simulation 2026-07-18 22:07:47 +03:00
Hamza-Ayed e2a472356c fix: remove + from App Review accounts to match normalized format 2026-07-18 21:12:24 +03:00
Hamza-Ayed 79cb19d4f8 feat: setup app review accounts and automated E2E simulation 2026-07-18 21:08:42 +03:00
Hamza-AyedandClaude Opus 4.8 836c77bb08 fix(e2e): point the simulation at the seeded tenant slug
السكربت أرسل x-tenant-id=tripz بينما المزروع على السيرفر «siro»، فردّ
الـAPI بـ401 Unknown tenant. صار قابلاً للتجاوز عبر TENANT_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:50:46 +03:00
Hamza-Ayed 71f6ab92e4 fix: append /api prefix to endpoints in simulation script 2026-07-18 19:49:27 +03:00
Hamza-AyedandClaude Opus 4.8 964b076c84 refactor(worker): declare the flusher directly instead of importing LocationsModule
جذر العلّة وراء أعطال الـDI الأربعة: WorkerModule استورد LocationsModule
كاملة، فجرّت الجيوفنس ← الإشعارات ← المستخدمين ← المستأجرين، وأربعة
متحكّمات HTTP تُنشأ في عملية لا تخدم HTTP.

المفرّغ يحقن Redis ومستودعَين لا غير. بإعلانه مباشرةً تسقط الحاجة إلى
I18n و Cache و Entitlements التي أضفتها في الدورتين السابقتين.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:44:41 +03:00
Hamza-AyedandClaude Opus 4.8 2a5adcf1fa fix(worker): register EntitlementsModule — closes the worker DI graph
آخر ورقة في السلسلة: Notifications → Users → Tenants → Entitlements،
وتبعياتها مستودعٌ و CacheService فقط. الباقي مستودعات TypeORM و
ConfigService العالمي، فلا طبقة خامسة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:40:22 +03:00
Hamza-AyedandClaude Opus 4.8 9bc52dbb9e fix(worker): register I18nModule and CacheModule in the worker composition root
الجيوفنس جرّ الإشعارات إلى شجرة الـworker لأول مرة، والإشعارات (و
UsersService خلفها) تحقن I18nService و CacheService. الوحدات @Global
تُسجَّل فقط إن استوردها أحد في الشجرة — وما يسجّله AppModule لا يصل
إلى جذر تجميع آخر.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:37:21 +03:00
Hamza-AyedandClaude Opus 4.8 ba7c99a05b fix(billing): import PaymentGatewaysModule so BillingService can resolve the registry
الـAPI كان في حلقة سقوط: BillingService يحقن PaymentGatewayRegistry، لكنه
يعيش في PaymentGatewaysModule الذي يستورده PaymentsModule دون إعادة تصدير.
الاستيراد المباشر يجعل التبعية صريحة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:32:39 +03:00
Hamza-AyedandClaude Opus 4.8 5b101bca57 fix(tests): clear geofence mock before the insignificant-ping assertion
النبضة الأولى في الاختبار ذات دلالة وتنادي الجيوفنس، فالعدّاد لم يكن صفراً
عند فحص النبضة الثانية. خطأ في الاختبار لا في الخدمة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:29:32 +03:00
Hamza-AyedandClaude Opus 4.8 4db7af9301 fix(tests): inject GeofenceService into DriverLocationService spec
الاختبار بقي على constructor بثلاثة وسائط بعد إضافة الجيوفنس، فأسقط
`npm test` وأوقف بناء الصورة — فلم يقلع الـAPI أصلاً على 4010.

يضيف أيضاً تغطية للتعاون الجديد: الجيوفنس لا يُنادى على نبضة بلا دلالة،
ولا يُفشل النبضة إذا سقط هو.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:25:57 +03:00
Hamza-Ayed ab5f2938d5 fix: resolve typescript arguments error for notifications 2026-07-18 19:22:03 +03:00
Hamza-Ayed 736af62344 fix: correct method name for sending push notification 2026-07-18 19:19:57 +03:00
Hamza-Ayed 741c6d36de fix: resolve import path error in geofence controller causing container crash 2026-07-18 19:11:49 +03:00
Hamza-Ayed fef1235fc9 feat: add location intelligence engine and E2E simulation script 2026-07-18 19:08:52 +03:00
Hamza-Ayed a15e65c42e feat: implement geofence using redis pub-sub 2026-07-18 18:48:25 +03:00
Hamza-Ayed cc5bc8c867 fix: change transit db port to 5434 to avoid conflict 2026-07-18 18:40:44 +03:00
Hamza-Ayed 12d576a5bc feat: add multi-stage Dockerfile for production NestJS deployment 2026-07-18 18:29:16 +03:00
Hamza-Ayed a90b31c3ad fix: Resolve TypeScript build errors in billing service 2026-07-18 18:21:54 +03:00
Hamza-Ayed f30209b1d7 feat: add transit microservice and supervisor dashboard 2026-07-18 18:18:40 +03:00
Hamza-Ayed 4aeab5275a feat: Add tenant wallet, billing tables, and transit microservice 2026-07-18 18:15:23 +03:00
Hamza-Ayed d239f51d5d feat: implement billing module for tenant subscription management and super-admin payment processing 2026-07-18 17:26:16 +03:00
Hamza-AyedandClaude Sonnet 5 d4ac38ca87 feat: P1 — بوابات دفع حقيقية (PayMob) + إغلاق ثغرة webhook حرجة
الأهم أولاً: `PaymentsService.webhook()` كان يقبل أي جسم `{ payment_id }`
بلا أي تحقّق توقيع — من يعرف معرّف دفعة معلَّقة كان يستطيع تحويلها «ناجحة»
ويشحن رصيداً من عدم (محفظة راكب أو رصيد سائق تشغيلي). الآن كل تغيير حالة
محروس بـ`adapter.verifyWebhook(headers, payload, tenant)`، ولا شيء يُقرأ من
الحمولة قبل ذلك كقرار ثقة — قراءة المرجع لتحديد المستأجر ليست قراراً.

البنية (docs/07 · docs/24 — P1):
- `PaymentAdapter`: charge() يعيد instant (كاش) · redirect (بوابة API حقيقية)
  · invoice (بلا API، تسوية عبر P2).
- PayMob (مصر): تسلسل auth→order→payment_key→iframe حقيقي عبر fetch،
  وتحقّق HMAC-SHA512 على تسلسل حقول ثابت (بروتوكول PayMob الرسمي بالضبط)
  بمقارنة ثابتة الزمن. مفاتيح كل مستأجر مستقلة — حساب تاجر خاص به.
- كليق/شام كاش/MTN/سيرياتيل/زين كاش: محوّل مشترك واحد لأن سلوكها متطابق
  فعلياً في سيرو (`create_*_invoice.php` تُنشئ فاتورة فقط، لا نداء بوابة
  حيّاً) — مرجع + حساب استلام معروض، والتسوية عبر رسالة SMS لا webhook.
  MTN/سيرياتيل الحقيقيَّين (توكن+OTP) موثَّقان كبند مفتوح: لا نبني تكاملاً
  لا نملك اعتماداً حيّاً للتحقّق منه.
- `PATCH /payments/settings` لأدمن المستأجر: مفاتيح PayMob · حسابات
  الاستلام · سرّ webhook الرسائل — الاستجابة لا تُعيد الأسرار.

تنظيف: إزالة الإشارات المتبقّية لحاوية `martin` من docs/07 (أُزيلت فعلياً
سابقاً)، وتحديث هيكل الكود الموثَّق ليطابق ما هو مبنيّ فعلاً.

25 اختباراً جديداً (226 إجمالاً) — منها توقيع PayMob محسوب فعلياً ومُتحقَّق،
وتلاعبٌ بالحمولة بعد التوقيع يُرفض، وسبع حالات تثبت إغلاق ثغرة الـwebhook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 15:50:05 +03:00
Hamza-AyedandClaude Fable 5 da035e46a4 feat: P2 — التسوية بالرسائل + إزالة مارتن + مستند البنية (docs/25)
P2 (كليك/شام كاش بلا API):
- الرسالة تُحفظ خاماً **قبل** أي تحليل: التحليل قد يفشل فنحتاج الأصل لإعادة
  المعالجة، وعند النزاع يكون النصّ الأصلي هو الحجّة لا تفسيرُنا له.
- النقطة تصنع المال، فرسالة مزوّرة = رصيد من عدم. الحماية: سرّ لكل مستأجر
  بمقارنة ثابتة الزمن (المقارنة النصّية تسرّب السرّ حرفاً حرفاً زمنياً)،
  وبصمة محتوى فريدة تمنع احتساب إعادة الإرسال مرتين.
- Gemini بحرارة صفر ومطالَب بإرجاع null عند عدم اليقين: نموذج يخمّن مبلغاً
  يسوّي فاتورة بمال لم يصل. بلا مبلغ صريح → مراجعة بشرية لا تسوية.
- المطابقة بالمرجع أولاً، ثم بالمبلغ خلال 24 ساعة وبشرط فاتورة وحيدة —
  فاتورتان بنفس المبلغ التباسٌ يُراجَع، لا تسويةٌ عشوائية لإحداهما.
- التسوية تمرّ بـmarkSuccess نفسه فلا يتفرّع مسار مالي ثانٍ.

إزالة حاوية martin: انطلق منصّة قائمة بذاتها لها خوادمها؛ دورنا طلب وردّ لا
استضافة خرائط (قرار المالك).

docs/25: جرد الحاويات · لماذا الدفع ليس خدمة منفصلة · سيرفر السوبر-أدمن
المنفصل ونطاقه الفرعي · WebSocket مقابل الاستطلاع بالأرقام · أحجام السيرفرات
على أساس الذروة لا المعدّل · نقل مستأجر · ترتيب التوسّع.

13 اختباراً جديداً (201 إجمالاً، كلها خضراء).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:19:17 +03:00
Hamza-AyedandClaude Fable 5 b1a060c5ed feat: P0/P3/P5 — دفترا المستأجر ورسم العملية وتوجيه المال
الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا.

- جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول
  واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب.
  مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث.
- فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من
  القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة.
- رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد
  المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب.
- توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة ·
  الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً.
- ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup`
  فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن.
- `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه.
- `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر.

25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ
صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:04:45 +03:00
Hamza-AyedandClaude Fable 5 4ca932dd1f fix: عمود overview + قرارات المالك (الخرائط · فلسفة المال · تنظيف المستودع)
إصلاح انحدار أدخلتُه: /admin/overview كان يستعلم عن `t.created_at` وهو غير
موجود في كيان الرحلة (الاسم `requested_at`)، فترجع النقطة 500 ويُفرَّغ جدول
المستأجرين في لوحة السوبر-أدمن. فحص الأنواع لم يمسكه لأن استعلامات
QueryBuilder نصوص، والمحكّ الذي كان سيمسكه لم يُشغَّل.

قرارات المالك 2026-07-18:
- الخرائط: `intaleq_maps` حصراً (SDK انطلق على MapLibre). تصحيح قرار سابق
  خاطئ: flutter_map + latlong2 طبقة منافسة تعطي نوعَي LatLng متضاربين.
  السبب ليس عمل جوجل في سوريا — بل قِدَم بياناتها في المنطقة.
- docs/24 جديد: فلسفة المال — فصل الإيراد (شحن السائق · رسوم العمليات) عن
  الأمانة (شحن الراكب)، بمحفظتين لا بحقل حالة، ودفتر مضاف فقط.
- P موسّعة (بوابات · تسوية بالرسائل لكليك/شام كاش · رسوم بالدولة · تقارير)
  و O5 (ترتيب المهام المجدولة)، وتأجيل Android Auto/CarPlay.
- الحزم: نسخ سيرو الأحدث + live_activities لشاشة القفل في iOS.

تنظيف: إخراج 463 ملف بناء NDK من التتبّع، وإخراج شهادات التوقيع ومفتاح
App Store من المستودع (تُولَّد من جديد عند الحاجة) مع تحديث .gitignore.
الملفات باقية على القرص — أُزيلت من الفهرس فقط.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:37:26 +03:00
Hamza-AyedandClaude Fable 5 de4bbd00ac feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند:
- تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً،
  وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً.
- OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي
  يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار
  `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع.
- N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر
  قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر.

N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)،
ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird.

فلاتر:
- docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام).
- السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات
  التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب.
- طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط.
- الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:06:46 +03:00
Hamza-AyedandClaude Fable 5 3ab74a13a8 feat: N1 — خدمة اللوحات + فرض تعليق المستأجر + النظرة الشاملة
- اللوحات الثلاث تُخدم من نفس أصل الـAPI (/panel/{superadmin,admin,service})
  عبر useStaticAssets + bind mount، فترث TLS القائم بلا دومين ولا CORS.
- تعليق المستأجر كان زخرفة: tenant.status يُكتب ويُعرض بلا أي فرض. الآن
  يُفرض في JwtStrategy (نقطة واحدة) + AuthService.resolveTenant، مع إبطال
  الكاش ليسري فوراً، وتمرير عند تعذّر القراءة حتى لا تسقط المنصة كلها.
- GET /admin/overview: رحلات · GMV · إيراد المنصة لكل مستأجر باستعلام
  واحد مجمَّع (لا N+1).
- توثيق قرار طبقات التطبيق (const flags + tree-shaking · أصيل موحّد
  لـShorebird · طبقتا الإعداد) في docs/22 §1.5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:35:20 +03:00
Hamza-AyedandClaude Opus 4.8 5cad6adbde feat: N4/N5 — لوحتا أدمن المستأجر وخدمة العملاء (ويب)
نقاط سرد إدارية (خلف RolesGuard admin/dispatcher):
- GET /drivers/admin/list — سائقو المستأجر (بترشيح الاعتماد)
- GET /trips/admin/list — رحلات المستأجر (بترشيح الحالة/الراكب)
- GET /payouts/admin/list — سحوبات المستأجر (بترشيح الحالة)
- GET /admin/users/search?phone= — بحث خدمة العملاء (يُطبَّع ويُبحث بالفهرس
  الأعمى، لا يُعرض الرقم الخام)

اللوحتان (SPA مكتفية ذاتياً، vanilla JS، RTL):
- admin-web: دخول أدمن (هاتف+OTP) → سائقون (اعتماد) · رحلات · مراجعة وثائق
  (قبول/رفض) · سحوبات (تحويل/فشل). تحقّقت أنها تُصيَّر نظيفاً في المعاينة.
- service-web: بحث مستخدم برقمه → بياناته ورحلاته + تفاصيل رحلة بالمعرّف.

المصادقة هاتف+OTP → JWT بدور admin/dispatcher (نفس الموبايل). الحماية على
السيرفر؛ الواجهة عرض فقط. الوصول بـ?tenant=<slug>.

E2E موسّع: يثبت أن السائق/الراكب (غير أدمن) يُرفضان من النقاط الإدارية (403).
الشكاوى تحتاج وحدة complaints (لاحقاً). لا هجرة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 03:55:23 +03:00
Hamza-AyedandClaude Opus 4.8 c93be38622 feat: المجموعة N (طبقة الـSaaS) — تزويد المستأجر + مانيفست التطبيق + لوحة سوبر-أدمن
الهدف الأساسي: السوبر-أدمن ينشئ مستأجراً كاملاً (اسم/لوغو/دولة/دفع/باقة/
ميزات/bundle IDs) ويولّد تطبيقه.

الباك إند (خلف PlatformGuard):
- POST /admin/tenants/provision — تزويد بضربة واحدة
- GET /admin/tenants/:id/summary + /app-manifest (يستهلكه سكربت البناء)
- PATCH .../branding + .../payment-methods
- POST /admin/tenants/:id/logo (رفع) + GET /tenant/logo/:slug عام
  **يخدم أصل الهوية فقط لا مجلد التخزين كاملاً** — وإلا سُرّبت صور وثائق
  الهوية الحساسة بمفاتيحها
- كتالوج payment-methods.ts (cash/wallet/cliq/zaincash/paymob/mtn/
  syriatel/shamcash حسب الدولة) + تصفية المخترع
- GET /admin/features يرجع الميزات ووسائل الدفع للكتالوج

الواجهة: dashboards/superadmin-web/index.html — SPA مكتفية ذاتياً (vanilla
JS، بلا خطوة بناء)، سرّ المنصة في sessionStorage، RTL. تُخدَم كملف ثابت.

سكربت التوليد: scripts/generate-tenant-app.sh — يجلب المانيفست ويضبط
الهيكل؛ خطوات فلاتر موسومة [Q] تُوصَل عند بناء مشروع فلاتر.

اختبار: provision-test.mjs (يثبت الحارس + التزويد + تصفية الدفع +
الاستحقاقات + المانيفست). لا هجرة — settings/branding jsonb موجودان.

القرار: اللوحات ويب (لا فلاتر) — تحديث فوري، سطح مكتب، معيار الصناعة.
البدء بـN (طبقة الـSaaS) قرار المالك.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 03:39:02 +03:00
Hamza-AyedandClaude Opus 4.8 4fc79181ff docs: الخريطة الشاملة (22) — من باك إند مكتمل إلى منصّة كاملة مماثلة لسيرو
مبنية على دراسة كود سيرو الفعلي: كتالوج سطح الميزات (تطبيقات · محرّك تسعير
· محرّك تسويق · بوتات · مواصلات · سياج جغرافي · متحكّم موقع السائق).
المجموعات L–Q: التسعير · الميزات المتقدّمة (المفاوض/الشرائح/السياج) · طبقة
الـSaaS (سوبر-أدمن + توليد التطبيق + سكربت Shorebird) · التحليلات/المواصلات/
البوتات · بوابات الدفع الحقيقية · إعادة بناء فلاتر. مع توصيات على القرارات
المفتوحة: ويب للوحات، مواصفات السيرفر، كتالوج الميزات لكل فئة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 03:26:47 +03:00
Hamza-AyedandClaude Opus 4.8 7703c23378 docs: تصحيح أمر E2E — نسخ السكربت بجوار node_modules (ESM لا يقرأ NODE_PATH)
socket.io-client كان يُثبَّت في /tmp لكن السكربت في /s، وESM يبحث عن الحزم
من مجلد السكربت لا cwd. الأمر الصحيح ينسخ السكربت إلى /tmp قبل التشغيل.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:50:58 +03:00
Hamza-AyedandClaude Opus 4.8 1512bcb5d7 fix: E2E — توليد رقم أردني صالح (10 خانات) لتسجيل الدخول
المولّد السابق أنتج 11 خانة فرفضه تطبيع الهاتف (الأردن يريد 0 + 9). صار
uniqJoPhone ينتج 07 + 8 خانات = 10، و uniqStr للرقم الوطني/اللوحة (لا تمرّ
بالتطبيع).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:47:43 +03:00
Hamza-AyedandClaude Opus 4.8 feb2a9663d feat: نقطة الرحلة المعلّقة للتقييم + اختبار E2E شامل
نقطة التقييم عند الفتح (طلب المالك): GET /trips/rating/pending يرجع أحدث
رحلة منتهية لم يقيّمها المستخدم (سائقاً أو راكباً) + العدد. التطبيق يناديها
عند كل فتح ليفرض شاشة التقييم قبل رحلة جديدة. TripsService.
finishedForParticipant يجلب المنتهية، و RatingsService.pendingRating يطرح
المقيَّمة باستعلام واحد.

scripts/e2e-test.mjs: اختبار شامل متكامل لكل وظائف الباك إند —
- تسجيل الدخول/الحساب + تطبيع الهاتف (نفس الرقم بصيغتين = حساب واحد)
- إعداد السائق: تقديم + ملف (رقم وطني مشفّر) + مركبة + اعتماد + إعادة دخول
  للحصول على دور driver في التوكن
- الرصيد التشغيلي (مكافأة التسجيل) + عدم الحجب
- اتصال السائق ورفع الموقع (Redis) + المطابقة + /trips/available
- طلب رحلة (تسعير + عرض) + كل الحالات (assigned→...→completed→paid)
- WebSocket: اتصال JWT + رصد trip:update (اختياري، يتخطّى إن غاب
  socket.io-client)
- خصم العمولة من الرصيد التشغيلي عند الإنهاء
- تسوية المحفظة عند paid (خصم الأجرة من الراكب)
- التقييم: رحلة معلّقة → تقييم الطرفين → تُمسح المعلّقة + منع التكرار
- السحب بـOTP خطوتين (طلب بلا خصم → تأكيد بالرمز → حجز)

أوامر التشغيل في docs/15 (مع/بلا WebSocket). يتطلّب OTP_DEV_MODE=true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 20:05:29 +03:00
Hamza-AyedandClaude Opus 4.8 a58f611063 feat: المجموعة B شريحة 2 — نقاط توقف + جدولة + مطابقة الوجهة
B4 نقاط التوقف: stops jsonb؛ المسار والتسعير يمرّان بالنقاط بالترتيب
(أصل → توقف₁ → … → وجهة) بجمع أرجل المسار. بلا توقفات = رِجل واحدة كالسابق.

B5 الحجز المسبق: scheduled_at + حالة scheduled جديدة (لا تدخل Redis ولا
تُوزَّع الآن). ScheduledTripsSweeper يعمل داخل الـAPI لا الـworker — لأن
الإطلاق يبثّ عروضاً عبر RealtimeGateway الموجود في الـAPI فقط. آمن مع عدة
نسخ: releaseDueScheduled يستعمل UPDATE شرطياً (scheduled→searching) فلا
تُطلَق رحلة مرتين. فهرس جزئي على scheduled المستحقة.

B8 مطابقة الوجهة: is_destination_match + destination_match_discount +
destination_match_discount_pct في التعرفة، يُطبَّق في settleFare. الخصم
على الراكب والسائق يقبض المخفَّض كاملاً (يربح رحلة في طريقه أصلاً). مؤجَّل:
محرّك المطابقة الذي يضبط العلم (وضع وجهة السائق).

هجرة: TripStopsSchedulingMatch. حالة trip جديدة: scheduled (تنتقل إلى
searching أو cancelled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:49:10 +03:00
Hamza-AyedandClaude Opus 4.8 c2a4b9d1a6 feat: المجموعة C — بيانات السائق والمركبة (كيان مركبة + ملف السائق)
C1: جدول vehicles (مكافئ CarRegistration عند سيرو) — plate/vin مشفَّران،
make/model/year، color+color_hex (لتلوين السيارة في فلاتر)، fuel/owner/
category، is_default، status. سائق قد يملك أكثر من مركبة؛ VehiclesService
يضمن افتراضية واحدة دائماً (أول مركبة تلقائياً، حذف الافتراضية يرقّي غيرها).

C2: حقول الملف على drivers — gender، national_number (مشفَّر + فهرس أعمى
فريد لكل مستأجر، نفس نمط الهاتف)، name_arabic (مشفَّر)، birthdate، address،
الرخصة (type/categories/issue/expiry)، rejected_reason. عبر PATCH
/drivers/profile.

C3: ai_data + user_input (jsonb) على drivers و vehicles — مخرجات Gemini
مقابل مدخلات السائق، تُراكَم للمقارنة حقلاً بحقل.

C4: vehicle_photo min:2 في كتالوج الوثائق.
C5: نوع وثيقة face_liveness (فيديو) — الرفع يقبله بلا قيد mime.

قرار: المركبة كيان مستقل لا حقول مسطّحة (سيرو يفصلها بـisDefault). الحقول
القديمة على drivers تبقى للتوافق؛ المصدر الجديد vehicles.

هجرة: VehiclesAndDriverProfile (جدول vehicles + أعمدة السائق + فهرس فريد
جزئي على الرقم الوطني).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:43:47 +03:00
Hamza-AyedandClaude Opus 4.8 4e9d4d9be1 docs: تدقيق سيرو — 17 عيباً أمنياً مصنَّفاً بالخطورة (للمعالجة لاحقاً)
سجلّ قابل للمعالجة لكل ما رُصد في كود سيرو الحيّ أثناء بناء Tripz: IDOR
السحب · double-spend · تسرّب رسم 3500 · 5 كتابات بلا معاملة · مقارنة كلمة
مرور نصّية تتجاوز الـhash · كلمة مرور مشتركة ثابتة · OTP 3 خانات · مسح
جدول وفكّ تشفير للتحقق · AES-CBC بـIV ثابت · إلخ. كل بند بخطورته وإصلاحه
المقترح لسيرو وكيف عالجناه في Tripz.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:38:18 +03:00
Hamza-AyedandClaude Opus 4.8 8f6c46f0ac feat: D5 — OTP متعدد المزوّدين حسب الدولة (من سيرو) + قرار بلا كلمة مرور
مراجعة سيرو (auth/otp/): مصر تستعمل Kazumi SMS مع failover لواتساب،
وسوريا/الأردن Nabeh. عندنا كان مزوّد واحد فقط.

- integrations/otp/: واجهة OtpProvider + OtpDispatcher يوجّه حسب
  tenant.countryPack بسلسلة failover (eg: [kazumi, nabeh] · jo/sy: [nabeh])
- KazumiSmsProvider (مصر SMS) + NabehOtpProvider (غلاف على NabehService)
- كل مزوّد يرسل رمزاً نولّده نحن (Redis) — استبعدنا نمط Intaleq (يولّد الرمز
  بنفسه) حفاظاً على مصدر واحد للرمز
- موحَّد: AuthService.sendOtp و PayoutsService.issueOtp يمرّان بنفس المُوزِّع
  الآن — أُزيل كل استدعاء Nabeh مباشر. أي مسار يحتاج رسالة يستدعي الخدمة
  الموحّدة (طلب المالك)
- فشل السلسلة كاملة = ServiceUnavailable صريح، لا صمت

قرار المالك (2026-07-17): بلا كلمة مرور إطلاقاً. المصادقة = هاتف + OTP مرة
→ جلسة مربوطة بالجهاز (D2). التسجيل بالهاتف لا Google/Apple (محظوران في بعض
الدول). سيرو نفسه لا كلمة مرور حقيقية له (password = hash(email) وهمي،
ومقارنة نصّية غير آمنة في مسار التجربة). يطابق أوبر/كريم/inDrive.

مراجعة حدّ الطلبات في سيرو (RateLimiter.php): حدود مسمّاة لكل نوع + fallback
بملف عند تعطّل Redis (fail-closed). عندنا مسار OTP fail-closed أصلاً؛
تخزين Throttler على Redis مؤجَّل للتوسّع الأفقي الفعلي (موثّق في D4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:24:01 +03:00
Hamza-AyedandClaude Opus 4.8 c258a8c4b8 fix: D4 — حدّ الطلبات كان مُعطَّلاً كلياً رغم أنه يبدو مفعَّلاً
ThrottlerModule.forRoot(...) كان مسجَّلاً في app.module.ts منذ البداية —
بلا أي حارس يطبّقه (لا APP_GUARD ولا @UseGuards). التسجيل وحده لا يفعل شيئاً
في NestJS: كل نقطة في الـAPI، بما فيها verify-otp و payouts/*، كانت بلا أي
حدّ طلبات إطلاقاً منذ اليوم الأول.

- APP_GUARD → ThrottlerGuard يُفعّل الحدّ العام (120/دقيقة) على كل نقطة
- عدّاد محاولات لكل (مستأجر، رقم) في AuthService.verifyOtp — الحماية
  الحقيقية ضد تخمين الرمز لا الحدّ العام: رمز 4 خانات = 10000 احتمال،
  ومهاجم يدوّر IP يتجاوز أي حدّ بالـIP وحده. 5 محاولات ثم إبطال الرمز،
  نفس نمط payouts.service (I4)
- حدود أضيق للأهداف عالية القيمة: send-otp (كل إرسال يكلّف رسالة واتساب
  مدفوعة فعلياً)، verify-otp، payouts/request، payouts/confirm
- HealthController مُستثنى (@SkipThrottle) — مراقبة تشغيل بلا بيانات حساسة

قرار وعي بالمخاطرة: بنيت أولاً حارساً مخصَّصاً يتتبّع بالمستخدم المصادَق
لا بالـIP وحده (مهم لموبايل — NAT عند مشغّلي الجوّال يجمع آلاف المستخدمين
خلف IP واحد)، لكن تطلّب حقناً يدوياً دقيقاً (InjectThrottlerOptions/
InjectThrottlerStorage) — خطأ فيه يمنع إقلاع التطبيق كاملاً، ولا بيئة هنا
لاختبار NestFactory.create() قبل الدفع (jest ينشئ الخدمات يدوياً فلا يكشف
أخطاء DI لحارس عالمي). حذفته ورجّحت الأمان بالتتبّع الافتراضي — موثّق في
docs/17 D4 لتُنفَّذ حين يمكن اختبارها فعلياً على السيرفر.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:47:03 +03:00
Hamza-AyedandClaude Opus 4.8 253fbb4b2a feat: المجموعة E (كشف الاحتيال) + تشفير الهاتف at-rest
E — اتصال ثم إلغاء (من driver_ride_scam عند سيرو):
- E1: driver_called_passenger + last_call_at/by/count على الرحلة نفسها لا
  Redis فقط — حقيقة دائمة تفيد التحقيق بعد انتهاء الرحلة وزوالها من الكاش.
  يُسجَّل عند call:offer (بدء المكالمة الفعلي) في RealtimeGateway عبر
  Repository<Trip> مباشرة — لا استيراد TripsModule كاملاً (يستورد
  RealtimeModule أصلاً، فدورة استيراد)
- E2: FraudService.recordCallThenCancel — عدّاد يومي منفصل عن عدّاد الإلغاء
  العام بالساعة. 3/يوم = إنذار لا يمنع الإلغاء، 6/يوم = حظر مؤقت (طلب
  المالك حرفياً). يرتبط فقط إن كانت المكالمة خلال 30 دقيقة من الإلغاء

تشفير الهاتف at-rest (ملاحظة المالك) — بفهرس أعمى منفصل، لا تشفير ساذج:
- AES-256-GCM عشوائي IV يمنع WHERE phone = :x من المطابقة أصلاً (كل تشفير
  مختلف شكلاً حتى لنفس الرقم) — لو طُبِّق التشفير مباشرة بلا هذا لكسر تسجيل
  الدخول بالكامل بصمت
- users.phone_bidx = HMAC-SHA256(الرقم المطبَّع) — حتمي، يصلح للبحث
  وللقيد الفريد. القيد انتقل من (tenant_id, phone) إلى (tenant_id,
  phone_bidx) — القديم كان سيقبل تكرار نفس الرقم الحقيقي فعلياً
- findByPhone/create يحسبان البصمة؛ لا نقطة أخرى تقرأ phone بمساواة خام
- هجرة EncryptPhone تُشفّر الصفوف القائمة فوراً وتبني البصمة بأثر رجعي —
  استثناء متعمَّد من تسامح decrypt() المعتاد (الهاتف عمود بحث يومي، خطره
  كنص صافٍ أعلى من name)
- يعتمد على تطبيع D1: بصمة حتمية لنفس *النص*، فلا بد أن يصل الرقم مطبَّعاً
  قبلها وإلا عاد نفس الرقم بصيغتين ليصير رقمين مرة أخرى

هجرات: TripCallSignal · EncryptPhone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:25:18 +03:00
Hamza-AyedandClaude Opus 4.8 1eb79fee99 feat: المجموعة D — تطبيع الهاتف + ربط الجلسة بالجهاز
D1 — تطبيع أرقام الهاتف (JO/EG/SY):
- common/phone/phone.service.ts: مفتاح قانوني واحد لكل رقم حقيقي — يقبل
  صفراً محلياً/دولياً/+/00/بلا صفر ويرجع صيغة دولية موحّدة بلا +
- يعالج لبس مصر تحديداً الذي ذكره المالك: "01012345678" يُطبَّع بمفتاح 20
  الصحيح لا "2" — الخوارزمية عامة فلا تحتاج معرفة اللبس، فقط تحذف الصفر
  البادئ وتضيف مفتاح الدولة الحقيقي
- send-otp و verify-otp و platform/users/role تمرّ كلها بالتطبيع الآن؛
  كانت تخزّن/تبحث بالرقم الخام كما كُتب — رقم واحد بصيغتين = حسابان
- هجرة NormalizePhones تُصحّح حسابات الاختبار الموجودة على السيرفر، بحذر:
  تتخطّى أي تصادم بدل كسر القيد الفريد (tenant_id, phone)
- أُزيل config.callingCodes المكرّر — مصدر واحد للحقيقة

D2 — ربط الجلسة بالجهاز (نمط سيرو): **مبنيّ ومطفأ**
(AUTH_REQUIRE_DEVICE_BINDING=false) حتى يرسل فلاتر x-device-id. منفَّذ داخل
JwtStrategy.validate نفسها (passReqToCallback) لا كحارس يُضاف يدوياً لكل
متحكّم — فلا نقطة محميّة يمكن نسيانها. التوكن يحمل hash(deviceId) لا القيمة
الخام. verifyOtp/refresh يمرّران x-device-id من المتحكّم عند إصدار التوكن.

D3 — HMAC: منفَّذ فعلاً ضمن I6 (SigningService/SignatureGuard)، لا تكرار.

خطأ ضبطته قبل الدفع: كتبت مفتاح `auth:` ثانياً في configuration.ts —
كائنات JS تسمح بمفاتيح مكرّرة والأخير يطغى، فكان سيمحو otpDevMode/otpTtl/
otpLength بالكامل. دُمج في الكتلة الأصلية بدل مفتاح جديد.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:03:56 +03:00
Hamza-AyedandClaude Opus 4.8 5cf288b30b docs: تثبيت نتيجة TLS على السيرفر (منجَز ومُثبَت)
https://tripz-api.intaleqapp.com → 200 عبر nginx+شهادة.
http://194.163.173.157:4010 → Connection refused (لا 401 بل رفض اتصال).
110/110 اختباراً بعد إصلاح trip_id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:55:35 +03:00