Commit Graph
100 Commits
Author SHA1 Message Date
Hamza-AyedandClaude Opus 5 0e6dd68385 حذف http2 من إعداد سوكيتات TLS — nginx المضيف أقدم من 1.25.1
nginx على السيرفر يرفض التحميل:
  nginx: [emerg] unknown directive "http2" in siro-sockets-tls.conf:30

التوجيه http2 on; لم يُضَف إلا في nginx 1.25.1. والحذف هو الصحيح لا مجرد
حلٍّ سريع: ترقية WebSocket تعتمد على ترويسة Upgrade في HTTP/1.1 ولا معنى
لها في HTTP/2 أصلاً (وproxy_http_version 1.1 مضبوط في الـ location).

تحذير تشغيلي: بعد up --force-recreate انتقلت الحاويتان إلى 127.0.0.1:12020
و13030، فإن فشل nginx -t يبقى المنفذان 2020/3030 بلا أي مستمع (Connection
refused) حتى ينجح reload. رتّب النشر: صحّح الإعداد ⇒ nginx -t ⇒ reload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 01:04:32 +03:00
Hamza-AyedandClaude Opus 5 afb5189515 إنهاء TLS أمام سوكيتات Workerman — إصلاح Socket Connect Error: timeout
السبب (مثبَّت على الإنتاج): التزام c35b350b بتاريخ 2026-07-23 — وهو التزام
تلقائي برسالة "Update: <time>" — أضاف المنفذ للروابط:
  -  'https://jordan-siro.intaleqapp.com'
  +  'https://jordan-siro.intaleqapp.com:2020'
وكانت القيمة العاملة قبل ذلك بلا منفذ (443) فيُنهي البروكسي TLS ويمرّر.

بإضافة :2020 صار التطبيق يضرب حاوية Workerman مباشرة، وهي تفتح المنفذ نصاً
صريحاً (new SocketIO(2020) بلا سياق SSL)، فمصافحة TLS تتجمّد حتى المهلة:
  ❌ Socket Connect Error: timeout
  ❌ Socket Connect Timeout: 20000
ونتيجته أن السائق لا يبعث update_location إطلاقاً ⇒ لا GPS في Redis ⇒ لا
موقع للراكب لا عبر السوكيت ولا عبر الـ polling (كلاهما يقرأ من نفس المصدر).

مثبَّت بالأدلة على السيرفر:
- ss: المنفذان 2020/3030 يملكهما docker-proxy (نص صريح)، و443 nginx.
- curl على 127.0.0.1:2020 يرد {"sid":...,"upgrades":["websocket"]} — أي أن
  الحاوية سليمة تماماً والناقص هو طبقة TLS وحدها.

الحل بلا أي بناء للتطبيقات (وبلا تنزيل مستوى الأمان — الـ JWT يُرسَل في
query string فلا يجوز إطلاقاً تحويله إلى http):
- الحاويتان تُنشران على 127.0.0.1:12020 و 127.0.0.1:13030 فقط.
- nginx على المضيف يستمع على 2020/3030 بشهادة الدومين ويمرّر إليهما مع
  ترقية WebSocket (nginx/siro-sockets-tls.conf).
- proxy_read/send_timeout 3600s: اتصال السائق يعيش ساعات، وبلا ذلك يقطعه
  nginx كل 60ث فتدخل الحاوية حلقة إعادة اتصال دائمة.
- proxy_buffering off و access_log off (نبضات GPS تُغرق القرص).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 01:01:42 +03:00
Hamza-AyedandClaude Opus 5 6fec88251c إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي
استرجاع شبكة أمان موقع السائق (انحدارة من الالتزام ab3be7e2):
- كان شرط "Smart Mode" يتخطّى _startDriverLocationPollingWithTimer كلياً إذا
  جاء القبول من السوكيت. قبل إصلاح ترتيب الاتصال كان السوكيت يخسر دائماً
  فالـ polling يعمل ويحمل الموقع؛ وبعد أن صار يفوز تعطّلت شبكة الأمان.
- وصول حدث القبول لا يعني أن تدفّق المواقع يعمل: التدفّق له شرط مستقل —
  تطبيق السائق يحقن passenger_id في update_location فقط إذا كان rideStatus
  نشطاً و BoxName.passengerID مكتوباً، وهو لا يُكتب إلا بعد فتح صفحة خريطة
  السائق (map_driver_controller.dart:2444).
- الآن الـ polling يعمل دائماً، و handleDriverLocationUpdate يوقفه تلقائياً
  بعد 3 تحديثات ناجحة من السوكيت — وهذا المنطق كان موجوداً أصلاً وهو التصميم
  المقصود. السوكيت يبقى المسار السريع بلا فقدان شبكة الأمان.

عدم تعطيل إنشاء الرحلة بانتظار سوكيت غير قابل للوصول:
- ensureConnectedBeforeRide كانت تنتظر 4 ثوانٍ قبل كل طلب. سوكيتات
  Workerman تستمع نصّاً صريحاً على 2020/3030 بينما التطبيقان يستخدمان
  https:// عليها (nginx لا يمرّرها — انظر docker/nginx/default.conf)، فمصافحة
  TLS ضد منفذ غير TLS تتجمّد حتى المهلة ⇒ 4 ثوانٍ مهدورة في كل رحلة.
- المهلة صارت 2ث، وأُضيف cooldown دقيقتين: بعد فشل قريب لا ننتظر إطلاقاً بل
  نحاول الاتصال في الخلفية ونكمل الطلب فوراً.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 00:51:18 +03:00
Hamza-Ayed b76eccb763 إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي 2026-07-27 00:25:02 +03:00
Hamza-AyedandClaude Opus 5 a26472ed9f إصلاح مسار الإشعار→الطلب عند الكابتن + توحيد إعادة اتصال سوكيت الراكب
سوكيت الراكب:
- setReconnectionAttempts: من 20 إلى لا نهائي، توحيداً مع تطبيق السائق. بعد
  20 محاولة كان يستسلم **نهائياً** فيصمت للأبد بعد دقائق من شبكة سيئة ويبقى
  الراكب على الـ polling بلا أن يدري — وخطره أكبر الآن لأن الاتصال صار يعيش
  من فتح الخريطة لا من إنشاء الرحلة.
- الـ heartbeat من 15ث إلى 30ث: Socket.IO يعمل ping/pong على مستوى
  البروتوكول ومستمع 'heartbeat' في passenger_socket.php فارغ أصلاً.

البدء البارد على iOS (سبب "التطبيق يغلق ونرجع من أول"):
- didChangeAppLifecycleState لا يُنفَّذ عند إطلاق التطبيق (يبدأ في resumed
  بلا انتقال حالة)، وهو المُنادي الوحيد لـ _checkPendingTrip ⇒ الضغط على
  الإشعار والتطبيق مقتول لا يوجّه لصفحة الطلب إطلاقاً.
- وحتى لو نُفِّذ، كان يستسلم على '/' ويحوّل المسؤولية لـ
  HomeCaptainController الذي لا يملك أي منطق كهذا ⇒ الطلب يبقى في التخزين
  للأبد. صار addPostFrameCallback + مُجدوِل يعيد المحاولة حتى خروج التطبيق
  من شاشة البداية (سقف ~30ث)، ولا يحذف الطلب قبل التوجيه.
- حرس إضافي: لا اقتحام لرحلة نشطة بطلب قديم، لا توجيه مكرر إذا كنا على
  OrderRequestPage، وتنظيف الحمولة التالفة.

TripOverlayPlugin أندرويد فقط بلا أي حرس منصّة:
- كل دالة كانت تنادي invokeMethod مباشرة ⇒ MissingPluginException على iOS
  (لا يوجد ios/ ولا مدخل في pubspec.plugin.platforms).
- الأخطر: backgroundMessageHandler كان ينادي showOverlay **قبل** حفظ
  pending_driver_list، فيسقط الاستثناء ويُلغي الحفظ كلياً على iOS. عُكس
  الترتيب — الحفظ أولاً دائماً، وهو الحدّ الأدنى الذي لا يتوقف على منصّة أو
  صلاحية.
- أُضيف isSupported وحُصِّنت كل الدوال (ترجع false/no-op ولا ترمي أبداً)،
  فلا يُقطع أيضاً _initApp قبل _listenToOverlayEvents.

أخطاء index في حمولة add_ride.php (أربع نقاط قبول):
- 'Duration' كان index 4 وهو end_lng لا المدة ⇒ صار 15 (duration_text).
  أحد المواضع كان فيه تعليق "انتبه: تأكد من الإندكس الصحيح للوقت".
- 'passengerWalletBurc' كان 26 وهو price_for_driver (أرباح السائق) لا رصيد
  الراكب ⇒ صار 27 (passenger_wallet).
- الموضعان مكرران في main.dart و local_notification.dart و order_over_lay.dart
  و order_request_controller.dart — وُحِّدت كلها.
- حرس الطول في backgroundMessageHandler: كان length > 29 مع قراءة index 30
  ⇒ RangeError على قائمة طولها 30 بالضبط.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 00:07:27 +03:00
Hamza-AyedandClaude Opus 5 ab3be7e2e3 وصل سوكيت الراكب قبل إنشاء الرحلة — ليسبق FCM والـ polling
كان السوكيت مضموناً أن يخسر السباق الأول بنيوياً. ترتيب
startSearchingForDriver كان:
  ① await postRideDetailsToServer()  → add_ride.php يوزّع الطلب على الكباتن فوراً
  ② _addRideToWaitingTable()          (بلا await)
  ③ initConnectionWithSocket()        ← السوكيت يبدأ الاتصال الآن فقط

بين ① و③ نافذة = ذهاب/عودة HTTP + مصافحة WebSocket + قراءة الـ JWT، ثانية
إلى ثلاث (أسوأ على 3G). وغرف Socket.IO لا تخزّن الأحداث: لو قبل كابتن داخل
تلك النافذة فإن $io->to('passenger_X')->emit() يُرمى نهائياً لأن الغرفة
فارغة — لا طابور ولا إعادة إرسال. وكابتن ينظر لقائمة السوق يقبل في أقل من
ثانية، فالنافذة مُرجَّحة لا نادرة.

هذا يفسّر لماذا كان الـ 404 على getRideOrderID.php قاتلاً: السوكيت يخسر،
والـ FCM يصل بعد أن ضبط الـ polling _isAcceptanceProcessed=true فيُرفض،
والشبكة الاحتياطية الوحيدة مكسورة.

ترتيب السيرفر كان صحيحاً أصلاً (acceptRide.php ينادي
notifyPassengerOnRideServer قبل sendFCM_Internal) — المشكلة أن المستقبِل
لم يكن موجوداً بعد.

التغييرات:
- ensureConnectedBeforeRide(): يوصل وينتظر الانضمام للغرفة بسقف 4 ثوانٍ،
  ولا يُفشل إنشاء الرحلة عند انتهاء المهلة (نكمل ونتّكل على FCM/polling).
- startSearchingForDriver ينتظرها قبل postRideDetailsToServer.
- وصل السوكيت في initializeDataAfterLogin (عند فتح الخريطة) فيصير الانتظار
  صفراً عملياً في الحالة الطبيعية.
- _isConnecting يغطّي نافذة الـ await على الـ JWT حتى لا يفتح النداءان
  المتزامنان سوكيتين، وحرس على passengerID الفارغ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:26:23 +03:00
Hamza-AyedandClaude Opus 5 f66db7db42 إضافة getRideOrderID.php المفقود — المسار الاحتياطي لبيانات السائق
تطبيق الراكب يطلب /ride/rides/getRideOrderID.php منذ البداية، والموجود على
السيرفر getRideOrderIDNew.php فقط ⇒ 404 صامت في getUpdatedRideForDriverApply.

هذا يفسّر عرضين ظنّاهما منفصلين:
- الـ polling يسبق الـ FCM فيضبط _isAcceptanceProcessed=true ثم يأخذ 404،
  فيُرفض بعدها payload الـ FCM الذي يحمل driver_info كاملاً
  ("Already processed") ⇒ الرحلة مقبولة بلا معلومات سائق.
- ولأن driverToken يبقى فارغاً، ترد send_fcm.php بـ 400 Missing: target
  ⇒ رسائل الراكب للسائق لا تُرسَل إطلاقاً. (الاتجاه المعاكس كان يعمل بعد
  إصلاح getRideWaiting.php — نفس خطأ التشفير معكوساً.)

getRideOrderIDNew.php لا يصلح بديلاً: داخلي عبر get_connect.php →
validateInternalKey فلا يستطيع التطبيق مناداته، ولا يرجع
ratingCount/completedRides/driverTier.

النقطة الجديدة:
- connect.php (JWT)، والراكب من الـ JWT فقط لا من الطلب (حماية IDOR).
- نفس استعلام acceptRide.php وشكل رده حرفياً حتى يقرأه
  _fillDriverDataLocally بنفس المفاتيح.
- فك تشفير driverToken.token — بلا ذلك يصل التطبيق blob يستخدمه كـ FCM
  target فيرفضه FCM بـ 400.
- تفادي تصادم المفاتيح: getUpdatedRideForDriverApply يقرأ
  passengerName + last_name كاسم الراكب، فلقب السائق نُقل إلى
  driver_last_name واسم الراكب الحقيقي يُرجَّع في مكانه.
- ride DB هو المرجع مع fallback على primary، ورحلة بلا سائق ترد success
  لا failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:18:02 +03:00
Hamza-AyedandClaude Opus 5 143146c1b4 إصلاح سوكيت الراكب وتوحيد مسارات الإشعارات
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول):
- تطبيق الراكب كان يرسل id فقط بلا jwt، و passenger_socket.php يرفض أي
  اتصال بلا jwt ⇒ الراكب لا ينضم لغرفته أبداً ولا يستلم ride_status_change
  ولا driver_location_update. تظهر حالة القبول عبر الـ polling فقط بينما
  driver_info يصل بالسوكيت وحده. (سوكيت السائق يعتبر الـ jwt اختيارياً،
  ومن هنا جاء التباين بين التطبيقين.)
- cancelled_by_driver كان يسقط من switch حالات الراكب فيبقى معلّقاً بعد
  إلغاء السائق.
- حماية socket (late) من القراءة قبل التهيئة عند الانسحاب بلا jwt.

الإشعارات والرسائل (سبب "مرات توصل ومرات لا"):
- جدول tokens يخزّن توكن الراكب مشفّراً، و getRideWaiting.php كان يرجعه
  بلا فك تشفير ⇒ من يقبل من قائمة السوق يحمل blob مشفّراً يستخدمه كـ FCM
  target فيرفضه FCM بـ 400: لا إشعار قبول ولا رسائل. ومن يقبل من الـ
  dispatch/FCM يحمل نصاً صريحاً فتعمل. الفرق كان في طريقة القبول.
- acceptRide.php يحلّ التوكن من القاعدة دائماً ولا يثق بالعميل (أصحّ أمنياً).
- market_new_ride كان لا يحمل passengerId ولا الإحداثيات فتصل "null"؛
  أُضيفت بلا أي PII لأن الحمولة تُبَثّ لكل سائق قريب لا للفائز فقط.
- send_fcm.php: مهلة على OAuth (كان يعلّق حتى مهلة PHP فتُسقط الرسالة
  بصمت)، توحيد ding→default لأندرويد، وحقن title/body/tone في data
  مطابقةً لـ FcmService.
- تطبيق السائق يقرأ title/body من data أولاً مثل الراكب، ولا يعرض فقاعة
  فارغة للرسائل الصامتة.

السوكيت والإعدادات:
- forwardLocationToPassengerSocket كان يقرأ lat/lng والحمولة فيها
  latitude/longitude ⇒ المسافة تخرج ضخمة والـ throttle معطّل تماماً
  فيُعاد التوجيه مع كل نبضة GPS.
- notifyPassengerOnRideServer كان يرجع null بصمت مطلق عند حجب العنوان.
- العنوان الافتراضي لسيرفر الموقع كان nginx/loction_server/driver_socket.php
  وهو ديمون Workerman لا يُخدَم عبر nginx ⇒ صار socket_driver:2021.
  (LOCATION_API_URL بقي على nginx لأن api_get_nearby.php سكربت عادي.)
- ride_server/passenger_socket.php (النسخة التي يشغّلها Docker) كانت ناقصة
  كل كود مواصلاتي الموجود في passenger_server/ ⇒ نُقل مع REDIS_HOST.
- .env.example: ALLOWED_SOCKET_URLS يغطّي أسماء حاويات Docker، وإضافة
  PASSENGER_SOCKET_INTERNAL_URL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 15:47:23 +03:00
Hamza-Ayed e269cb9b70 إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي 2026-07-26 13:44:07 +03:00
Hamza-Ayed 330dfc6dba إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي 2026-07-26 13:15:45 +03:00
Hamza-Ayed 4b5235c35c إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي 2026-07-26 12:18:23 +03:00
Hamza-Ayed 16feb51a75 إصلاح توافق فلاتر مع Theme الجديد 2026-07-26 11:36:26 +03:00
Hamza-Ayed 3af99cc18a إصلاح توافق فلاتر مع Theme الجديد 2026-07-26 11:33:20 +03:00
Hamza-Ayed 7830efead7 إصلاح توافق فلاتر مع Theme الجديد 2026-07-26 04:28:09 +03:00
Hamza-Ayed ca6cb7a3fe تصميم عالمي جديد، وإصلاح الخرائط ومراقب السيرفرات 2026-07-26 04:21:42 +03:00
Hamza-Ayed f325ffce42 تصميم عالمي جديد، وإصلاح الخرائط ومراقب السيرفرات 2026-07-26 04:02:10 +03:00
Hamza-Ayed c43f801542 إصلاح الشاشة البيضاء ومسار base-href 2026-07-26 03:26:15 +03:00
Hamza-Ayed 2bacb1b9e1 تحديث شامل للوحة التحكم وإضافة كافة الميزات للـ WebSidebar 2026-07-26 03:22:47 +03:00
Hamza-Ayed 5ec54c03f5 تحديث شامل للوحة التحكم وإضافة كافة الميزات للـ WebSidebar 2026-07-26 02:58:59 +03:00
Hamza-Ayed a954f49307 Update: 2026-07-26 02:51:53 2026-07-26 02:51:54 +03:00
Hamza-Ayed 91fe0f78f7 Update: 2026-07-26 01:29:57 2026-07-26 01:29:57 +03:00
Hamza-Ayed 5f5b68a8cd Update: 2026-07-26 01:27:56 2026-07-26 01:27:56 +03:00
Hamza-Ayed 1664743ef9 Update: 2026-07-26 01:11:28 2026-07-26 01:11:28 +03:00
Hamza-Ayed 1dfc302a4f Update: 2026-07-26 01:03:40 2026-07-26 01:03:41 +03:00
Hamza-Ayed 76c8652bf0 Update: 2026-07-25 20:18:18 2026-07-25 20:18:18 +03:00
Hamza-AyedandClaude Opus 5 a095472f39 تتبّع composer.lock ومنع تكرار انهيار خادم الدفع
vendor/ و composer.lock كانا مستثنيين في .gitignore، فلم يصل أيٌّ منهما
إلى السيرفر. النتيجة أن realpath() في payment_server/v2/main/connect.php:3
ترجع false وينهار كل مسار /v2/main/ بـ Fatal error.

- إزالة قاعدتَي composer.lock من .gitignore (vendor/ يبقى مستثنى)
- الالتزام بملفات القفل لـ payment_server/v2 و ride_server و loction_server
- ترقية firebase/php-jwt في payment_server/v2 من ^6.11 إلى ^7.0 (v7.1.0)
  لأن 6.11.x محجوب بتحذير أمني PKSA-y2cr-5h3j-g3ys — لا تغيير في الشيفرة،
  الاستخدام محصور في JWT::encode/decode و Key وأصناف الاستثناءات
- تثبيت platform.php=8.2 ليطابق القفل صورة الحاوية لا PHP المحلي

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 20:11:59 +03:00
Hamza-Ayed c8a9e98ff5 Update: 2026-07-25 20:09:31 2026-07-25 20:09:31 +03:00
Hamza-Ayed e03b9c30d5 Update: 2026-07-25 18:43:44 2026-07-25 18:43:44 +03:00
Hamza-Ayed 20ea9aa12c Update: 2026-07-25 18:42:18 2026-07-25 18:42:18 +03:00
Hamza-Ayed 8d3e63d1c7 Update: 2026-07-25 18:40:38 2026-07-25 18:40:38 +03:00
Hamza-Ayed 4bbc687c15 Update: 2026-07-25 18:28:30 2026-07-25 18:28:31 +03:00
Hamza-Ayed 5e103f60f2 Update: 2026-07-25 18:19:51 2026-07-25 18:19:52 +03:00
Hamza-Ayed 27369b8ac1 Update: 2026-07-25 18:05:37 2026-07-25 18:05:37 +03:00
Hamza-AyedandClaude Opus 5 61d6380861 Fix SQL injection and stale status matching in the wallet endpoints
The three endpoints the driver app calls for its wallet built their queries by
interpolating the driver id straight into SQL. Anything the app sent went into
the statement, and these run against the payments database.

They also matched only status = 'Finished'. The current ride pipeline writes
'completed', so a driver's completed rides, pending payouts and weekly
earnings all read as zero regardless of how much they had driven — which is
what the wallet errors in the admin error log are sitting next to.

getAllPayment.php, driverStatistic.php and getCountRide.php now bind the id
and match either spelling. Verified no interpolated identifier remains and
every rewritten condition is balanced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 17:57:52 +03:00
Hamza-AyedandClaude Opus 5 3fb7bc5190 Show the build id, tame long table cells, and add purposeful motion
The build identifier now sits in the sidebar footer. "Is my deploy actually
live?" was only answerable by opening Session & Security, which is the wrong
place for the first question asked after every deploy.

Error-log rows carry stack traces and full URLs. They stretched their row far
past the viewport and pushed the other columns out of view, which is exactly
where the console is least usable. Values over 70 characters truncate and
expand on click.

Motion is limited to two things that mean something: content arriving
(staggered, so a grid resolves instead of snapping), and a fetch being in
flight — the refresh control spins and the status pill pulses, so a section
that legitimately returns nothing still shows that it tried. All of it is
disabled under prefers-reduced-motion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 17:49:48 +03:00
Hamza-Ayed f9110a7d92 Update: 2026-07-25 17:40:28 2026-07-25 17:40:29 +03:00
Hamza-AyedandClaude Opus 5 a0812dbd10 Fix broadcast delivery; make transit organisations manageable
Broadcasts never reached anyone. The internal FCM call defaulted to
127.0.0.1, which inside the php container is the php container itself — the
web server runs in a separate nginx container, reachable by service name on
the Compose network. Every send failed the curl and returned a generic 502.
The default now points at nginx, the URL is overridable via FCM_INTERNAL_URL,
and the error carries the actual reason and target instead of a bare status.

Transit organisations were a read-only count table with nothing to act on.
The module now supports the operations an admin actually needs:
- open an organisation for its counts, routes, recent trips and admins
- create one, including the founding administrator create.php requires
- edit city, contact details, contract status and trial end, with a
  confirmation when the contract changes since suspending cuts off service
- add an administrator, and enable or disable an existing one

Verified end to end against the endpoints' real payload shapes, including
that an incomplete create form is rejected before any request is sent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 17:10:51 +03:00
Hamza-AyedandClaude Opus 5 bc1b0129e8 Fix v2 analytics status matching; give Growth and Analytics real charts
Eight queries across the v2 modules counted only status = 'Finished' and so
reported zero on live data, where the current ride pipeline writes
'completed': realtime revenue for today and yesterday, financial stats,
settlements, driver scorecard, driver ranking, and both revenue queries. All
now match either spelling.

Growth and Advanced Analytics rendered through the generic shape-detecting
renderer, which produced raw tables that said little. Both now have purpose-
built views:

- Growth: totals, 30-day joins, and a two-series daily chart. growth.php only
  returns days that had signups, so the series is expanded to a continuous
  30-day axis with explicit zeros — plotting the returned rows directly would
  hide the gaps and make a quiet month look like steady growth. A caption
  states how many days actually had a signup.
- Analytics: revenue summary tiles, a daily revenue trend, and the captain
  ranking, with a note explaining that platform share is what remains after
  the captain's cut.

Null aggregates render as "—" rather than 0.00, and markers are drawn only on
days with a value so a flat zero line stays readable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:55:58 +03:00
Hamza-AyedandClaude Opus 5 2135edcf43 Close the remaining ciphertext joins and a SQL injection in email verification
- Customer-service notes joined to the account by comparing encrypted phone
  columns. Both notes tables now carry phone_key, written when a note is
  saved, and the three joins match on it.
- The email_verifications join was comparing a plaintext column against an
  encrypted one, so it never matched and `verified` was always NULL in both
  passenger and driver sign-in. It is now resolved in PHP against the
  decrypted address, which fixes a pre-existing bug rather than only
  preparing for GCM.
- auth/sendVerifyEmail.php built all three of its statements by interpolating
  the request values into SQL. Any caller could inject through the email or
  token field. Now parameterised.
- serviceapp/register.php duplicate detection consults the users indexes and
  writes them with the row.

Sweep confirms no join or lookup compares two encrypted columns any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:48:10 +03:00
Hamza-AyedandClaude Opus 5 35a66935aa Repair verification joins broken by the OTP key change; extend backfill
Storing the verification phone as a keyed HMAC fixed OTP lookups but broke
every query that joined those tables back to the account, because
phone_verification*.phone_number no longer holds the same value as
driver.phone / passengers.phone. Six joins were affected, and four of them
feed the `verified` flag that the rider and driver apps check at sign-in — so
this was already failing under the current CBC mode, not only after a switch
to GCM.

Accounts now carry phone_key, computed exactly as otpPhoneKey() does, and the
joins match on it. It is written at registration for both apps and populated
for existing rows by the backfill.

The backfill also covers the columns added for the remaining lookups:
users.email_bidx/phone_bidx and driver.national_bidx, which were migrated but
never populated, and honours a per-field prefix so phone_key reproduces
otpPhoneKey's exact output.

Insert column/value counts verified with a paren-aware parser after editing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:40:53 +03:00
Hamza-AyedandClaude Opus 5 8d7e3118b5 Migrate remaining encrypted-column lookups to the blind index
Completes the set of queries that matched a freshly encrypted value against a
stored one, which only works while encryption is deterministic. Each keeps its
original comparison and adds an index comparison in the same WHERE, so nothing
changes today.

- passenger sign-in by email, service-staff sign-in, Firebase token lookup
- driver lookup by phone and by national number
- admin ride lookup and ride monitor (both tables)
- nabeh: driver status, user resolution, ride history, complaint submission

transit_org_admins lives in the transit database and has no index column, so
login there falls back to decrypting the small set of active admins and
comparing normalised numbers.

Schema: adds users.email_bidx/phone_bidx and driver.national_bidx with their
indexes.

Verified that every :*_bidx placeholder introduced is actually bound — an
unbound one is a fatal error at request time, not a silent miss.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:36:32 +03:00
Hamza-AyedandClaude Opus 5 39b5a7fc7f Keep OTP phone numbers recoverable for customer-service follow-up
Storing the verification phone as a keyed HMAC made OTP lookups independent
of the encryption mode, but the hash is one-way — and customer service reads
those same rows to chase people who requested a code and never finished
registering. That workflow would have lost the number entirely.

The verification tables now carry both forms: phone_number holds the lookup
key, and a new phone_enc column holds the encrypted number, which is
decryptable when a human needs to call.

The two follow-up queries also compared the verification row against the
driver/passengers tables and the notes tables by matching ciphertext, which
only ever worked because encryption was deterministic. Under GCM every number
would have looked unregistered and every note would have disappeared. Both now
read the number from phone_enc and match on normalised plaintext, so they are
correct under either mode.

Rows written before phone_enc existed are skipped rather than shown without a
number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:27:29 +03:00
Hamza-AyedandClaude Opus 5 a1c19b052d Make OTP verification independent of the encryption mode
The verification tables (token_verification*, phone_verification*) use the
phone number as a lookup key: written when the code is sent, read when it is
checked. Storing it encrypted worked only because encryptData() is
deterministic — under AES-GCM the two sides would produce different
ciphertexts and no code would ever verify, locking every user out of
registration and OTP sign-in.

otpPhoneKey() stores a keyed HMAC of the normalised number instead. No schema
change is needed since the column is textual, local and international formats
now resolve to the same key, and the value cannot be reversed without the
pepper. It falls back to the previous behaviour when no pepper is configured.

Applied to both sides of every affected flow — request/verify, and the driver
and passenger send/verify pairs — including the OTP value itself where it is
compared by equality rather than decrypted. auth/otp/verify.php already
decrypts the token before comparing, so it needed no change there.

Also adds ENCRYPTION_MODE to EncryptionHelper: encryptData() writes GCM when
set to 'gcm', CBC otherwise. Verified in both directions — rows written under
CBC stay readable after switching, and rows written under GCM stay readable
after rolling back — so the switch is reversible by an environment variable.

The admin console's own OTP is unaffected: it keys the table by the stored
ciphertext read from adminUser, identical on both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:18:16 +03:00
Hamza-AyedandClaude Opus 5 c9b4d14da6 Route account lookups through the blind index and keep it fresh on write
These are the paths that must stop depending on deterministic encryption
before storage can move to AES-GCM. Each keeps its original ciphertext
comparison in the same statement, so behaviour is unchanged today and no
account becomes unreachable during the transition.

Lookups:
- auth/login.php — passenger sign-in matched the raw value against the
  encrypted column, which only works because encryptData() is CBC with a
  fixed IV.
- auth/passenger/register.php and auth/driver/register.php — duplicate
  detection. Without the index these would stop detecting existing accounts
  under GCM and allow the same phone to register twice.

Writes now populate the index in the same statement as the value:
- both registration paths write phone/email/name indexes with the row;
  driver indexes are computed before the encryption pass, since the raw
  values are unavailable afterwards.
- passenger profile update and admin driver update refresh the index when
  the underlying field changes. For the composite name index the untouched
  half is read back from the row.

Adds --audit to the backfill script: recomputes every index from its
encrypted value and reports missing or stale entries. Drift here is silent
by nature — it surfaces only when a real search fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:04:40 +03:00
Hamza-AyedandClaude Opus 5 15f55ff3e4 Add --verify to the backfill script
Confirms a value is findable through the blind index after backfilling,
without opening the console or the database. Prints matched row ids only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 15:59:50 +03:00
Hamza-AyedandClaude Opus 5 761b957c96 Silence REQUEST_METHOD warning when bootstrap runs from the CLI
The migration and backfill scripts load bootstrap.php outside a request, where
$_SERVER['REQUEST_METHOD'] does not exist, so every CLI run printed a warning
before its output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 15:55:18 +03:00
Hamza-AyedandClaude Opus 5 57e22477fb Add an idempotent CLI migration runner
phpMyAdmin is unreachable on this deployment, so schema changes need a path
that does not depend on it. migrate.php reuses core/Database, meaning no
credentials are passed on the command line, and checks information_schema
before each ALTER so re-running is safe and never fails on a duplicate
column. Supports --status and --dry-run.

Covers the blind-index columns and the missing adminUser status/approved_by/
approved_at columns. Every change is additive and nullable, so applying it to
a running database changes no behaviour on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 15:35:34 +03:00
Hamza-AyedandClaude Opus 5 6802026dbd Add blind-index search layer; fix captain detail 200-with-empty-body
Searching encrypted columns currently works only because encryptData() is
AES-CBC with a fixed IV, i.e. deterministic. That determinism is what leaks
equality and shared prefixes, and it is why moving storage to AES-GCM would
break every lookup. This separates the two concerns.

- core/Security/BlindIndex.php: HMAC-SHA256 over a normalised value, keyed by
  a secret pepper. Phone numbers have a small keyspace, so a bare SHA-256
  would be reversible by enumeration; the pepper lives in the environment, not
  the database. The scope string includes table and field so the same number
  does not produce a matching index across tables.
  Normalisation unifies local/international phone forms, lowercases emails and
  folds Arabic alef/ya/ta-marbuta and diacritics for names.
- migrations/: nullable *_bidx columns plus indexes, and the missing
  adminUser.status/approved_by/approved_at columns that admin approvals need.
- scripts/backfill_blind_index.php: restartable, batched, --dry-run capable,
  touches only index columns.
- Admin lookups by phone/email now match the index, keeping the old ciphertext
  comparison in the same query so search keeps working until the backfill runs.
  bootstrap exposes $blindIndex as null when no pepper is configured.

Also: AdminCaptain/getCaptainDetailsById.php selected driver.education, a
column absent from this schema. The PDOException was uncaught, so the client
received an empty body with HTTP 200 — the "non-JSON response" seen when
opening a captain. It now omits the column, catches the error, reports it as
JSON, and requires an admin role.

Console: opening any sidebar section refetches its data instead of showing
what was loaded when the console started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 15:16:09 +03:00
Hamza-AyedandClaude Opus 5 81ee2acefd Add removal/blacklist tools, Arabic UI, and a persistent sign-in error
Authorisation: driver/deleteCaptain.php permanently deletes a captain and had
no role check at all — any valid token, including a driver's or passenger's,
could delete captains. It now requires super_admin. The two unblacklist
endpoints had no role check either and now require an admin role.

Console:
- Blacklist & removal screen (super admin only): current blocks, permanent
  removal, and lifting a block. Deletion additionally requires the phone
  number to be retyped, because the row is deleted outright.
- Arabic interface with full RTL layout and a language toggle. Sidebar,
  content offset, input affordances, toasts and directional icons all mirror;
  numbers, identifiers and the diagnostics output stay LTR.
- Sign-in failures now render in the form and stay there. They were only
  shown as a toast, which disappears before it can be read — a locked
  account or a rejected device looked like the page simply returning to
  the login screen with no explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 14:53:26 +03:00
Hamza-Ayed 42f6d33efd Update: 2026-07-25 14:44:30 2026-07-25 14:44:30 +03:00
Hamza-AyedandClaude Opus 5 4009af8dd3 Add campaign launcher and app version manager; fix SQL injection in updatePackages
serviceapp/updatePackages.php built its UPDATE by interpolating the request
values straight into the SQL string, so any caller with a valid token could
execute arbitrary SQL through the version field. It now uses bound
parameters, requires an admin role, validates the version format, and writes
an audit entry.

trigger_campaign.php gains dry_run=1: it performs the same Gemini analysis
and target selection but returns before creating the promo code and before
dispatching any notification. Launching without previewing was the only
option before, and a launch writes a seven-day discount and pushes to every
passenger in the country.

Console:
- Campaign launcher with a mandatory preview. Launching stays disabled until
  the current parameters have been previewed, and re-locks if any parameter
  changes afterwards or once a launch completes.
- App version manager with the same version-format check as the server and a
  confirmation naming the old and new values.

Cache busting: assets are served straight off a bind mount with no version,
so browsers kept running the previously cached build after a deploy. Both
asset links now carry ?v=, and the build id is shown in Session & Security
and printed in the diagnostics report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 14:41:27 +03:00
Hamza-AyedandClaude Opus 5 67f55e5192 Add driver document review and staff onboarding
Driver documents: paged list from auth/driver/drivers_pending_list.php, a
detail panel showing each uploaded document as a thumbnail linking to the
full image, and activation via Admin/driver/updateDriverFromAdmin.php. The
confirmation states how many documents were reviewed and warns explicitly
when a captain has none on file, since approving then activates an
unverified account.

Details are requested as a POST body. The mobile app calls this endpoint as
GET "?id=", which filterRequest() never reads, so its detail lookup cannot
be receiving an id at all.

Staff: pending admin/service accounts with per-account activation via
Staff/activate.php, the employee list, and a creation form posting to
Staff/add.php. Administrator accounts are offered only to super admins,
matching add.php's own check; passwords are rejected below 8 characters and
cleared from the form after submission.

Both screens mask phone numbers for plain admins and never render
token/password/fingerprint fields.

Also stop .btn-primary stretching to full width when used inline in a card
header — it is styled for the login form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 14:30:49 +03:00
Hamza-AyedandClaude Opus 5 a0ab6c5155 Add broadcast notifications and transit route approvals
Broadcast: ride/firebase/send_fcm.php is an internal service guarded by a
shared secret, so the browser cannot call it — holding that key client-side
would expose it, and the endpoint cannot tell who the sender is. A new
Admin/notifications/broadcast.php sits in front of it: it runs behind
connect.php, requires super_admin, restricts the target to the two topics the
apps actually subscribe to ('drivers'/'passengers') so it cannot be used to
push to an arbitrary topic or a single device token, bounds the title and
body, writes an audit entry before dispatching, and only then forwards the
call internally with the shared secret.

The composer shows a live push preview and an explicit confirmation naming
the audience, since a broadcast cannot be recalled.

Route approvals: draft routes render with their stops, distance and stop
count, and approve/reject posts to transit/route/approve.php behind a
confirmation stating the consequence. Available to admins and super admins,
matching the endpoint's own role check.

Also render user-supplied text with unicode-bidi: plaintext — Arabic names,
addresses and messages were being laid out left-to-right inside the
English UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 02:15:02 +03:00
Hamza-AyedandClaude Opus 5 41a06bba0c Add fleet, invoice, promo and lookup-driven admin modules
Brings the console to 18 modules across 26 navigation entries.

- Fleet performance (best captains, per-captain card charges), invoice
  totals and promo codes as read-only panels.
- Ride monitor: monitorRide.php and getDriverGiftPayment.php answer only for
  a given phone number, so they get an input rather than a panel that would
  render an error on load.
- Status-like values in generated tiles use the same label mapping as the
  tables, so raw values such as cancelled_by_passenger no longer leak into
  the UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 02:02:08 +03:00
Hamza-AyedandClaude Opus 5 db4ca7dd7a Fix dashbord.php parse error; require super_admin on pricing and crypto tools
Hotfix: a comment added to the dashboard SQL contained double quotes inside
the double-quoted PHP string, terminating it and making dashbord.php fail to
parse. Production was returning a parse error for every dashboard request.

Authorisation gaps closed — connect.php only proves a token is valid, it does
not check what the caller is allowed to do:

- Admin/ggg.php decrypts any database field and was authorised solely by an
  admin phone number sent in the request body. Anyone who knew a listed
  number could decrypt platform data without signing in. It now runs behind
  connect.php, requires super_admin, keeps the phone list as a second factor,
  and records every use.
- ride/kazan/update.php, kazan/add.php and ride/promo/{add,update,delete}.php
  changed live pricing and discount codes with no role check at all, so any
  valid token — including a driver's or passenger's — could rewrite the fare
  table. All now require super_admin.

Staff/pending.php: adminUser has no `status` column in this deployment, so
the query failed with an opaque "unavailable". It now checks for the column
and reports the actual reason.

Console: Kazan tariff editor for super admins — sends only changed fields,
shows an old → new confirmation before saving, and stays read-only with an
explanatory notice for plain admins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 02:00:14 +03:00
Hamza-AyedandClaude Opus 5 852c6ece5c Fix admin status handling for the current ride pipeline; extend console
The ride table holds two generations of status values: the legacy CamelCase
set ('Finished', 'CancelFromPassenger') and the lowercase set written by
backend/ride/rides/* today ('completed', 'cancelled_by_passenger'). Admin
queries only matched the legacy set, so on live data:

- get_rides_by_status.php returned nothing meaningful for every filter, and
  the "in progress" default masked it.
- dashbord.php reported total_driver_earnings as NULL, completed_rides as a
  fraction of the real count, and cancelled_rides as 0.
- driver_avg_duration averaged in negative durations, yielding "-00h 22m".

All three now match on LOWER(status) across both families.

Staff/pending.php ran with no authentication at all, exposing pending
admins' names and phone numbers to any caller; it now goes through
connect.php with a role check. It also returned HTTP 400 for everything when
the `users` table was absent — each source is queried independently and
reports its own availability.

Console:
- Render rides from either schema generation (price/date/time and
  start_location coordinates, or the older address/created_at columns).
- Null aggregates render as "—" rather than a measured 0.00.
- Add tariff/promo, WhatsApp send and encryption modules, all super-admin
  gated; pricing remains read-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 01:47:55 +03:00
Hamza-Ayed 915a148ebf Update: 2026-07-25 01:38:11 2026-07-25 01:38:11 +03:00
Hamza-Ayed 0d9095fd3f Update: 2026-07-25 01:16:32 2026-07-25 01:16:33 +03:00
Hamza-Ayed eda7018434 Update: 2026-07-25 01:02:01 2026-07-25 01:02:02 +03:00
Hamza-Ayed 822cb5963a Ensure Live Backend mode is enforced and clear old Demo localStorage session 2026-07-25 00:44:11 +03:00
Hamza-Ayed 8ec7b9aae6 Bind live MySQL database tables (Rides, Passengers, Pending Approvals) into Siro Admin Portal 2026-07-25 00:41:18 +03:00
Hamza-Ayed c9168c1c80 Enhance Siro Admin Dashboard with live backend API database bindings 2026-07-25 00:33:32 +03:00
Hamza-Ayed 10f1154cc7 Update adminUser fingerprint and fingerprint_hash in database upon successful OTP verification 2026-07-25 00:30:14 +03:00
Hamza-Ayed 9d2a665d64 Restore WhatsApp OTP (Nabeh API) flow for Admin authentication 2026-07-25 00:25:53 +03:00
Hamza-Ayed 4c738e8f43 Remove legacy encrypt_decrypt.php - bootstrap.php already provides encryptionHelper 2026-07-25 00:23:27 +03:00
Hamza-Ayed 032e1edda5 Remove broken providers.php require_once - issue JWT directly on valid password 2026-07-24 23:59:27 +03:00
Hamza-Ayed 1ca2c5a5dd Fix require_once path in login.php using realpath 2026-07-24 23:51:28 +03:00
Hamza-Ayed f115292dd2 Refactor login.php with decryption lookup for encrypted fields and restore Nabeh OTP integration 2026-07-24 23:48:57 +03:00
Hamza-Ayed 60e344598b Fix Admin login path issue and issue direct JWT on valid credentials 2026-07-24 23:45:28 +03:00
Hamza-Ayed 8bc9290916 Safely initialize encryptionHelper and handle Throwable in Admin login.php 2026-07-24 23:42:39 +03:00
Hamza-Ayed 9e2964d307 Allow email and ID login in Admin auth login.php and bind web device fingerprint 2026-07-24 23:38:17 +03:00
Hamza-Ayed b3126013f5 Update: 2026-07-24 23:31:37 2026-07-24 23:31:37 +03:00
Hamza-Ayed 5ebff42841 Update: 2026-07-24 23:23:54 2026-07-24 23:23:55 +03:00
Hamza-Ayed 2944f21f53 Add dashboard web applications (siro-admin & siro-service) with docker Nginx routing 2026-07-24 22:59:26 +03:00
Hamza-Ayed 917dfc025f Update: 2026-07-24 22:57:32 2026-07-24 22:57:32 +03:00
Hamza-Ayed 0b24bc21b6 Update: 2026-07-24 22:55:34 2026-07-24 22:55:34 +03:00
Hamza-Ayed 474c212bcb refactor: migrate amount fields to decimal and implement location service restriction for unsupported regions 2026-07-24 15:08:37 +03:00
Hamza-Ayed e7629a9eb9 Update: 2026-07-23 22:14:10 2026-07-23 22:14:10 +03:00
Hamza-Ayed 695d6d7cb2 Update: 2026-07-23 21:18:25 2026-07-23 21:18:25 +03:00
Hamza-Ayed e7aa28fe3d Update: 2026-07-23 21:05:22 2026-07-23 21:05:22 +03:00
Hamza-Ayed 7d646b579b Update: 2026-07-23 21:02:51 2026-07-23 21:02:51 +03:00
Hamza-Ayed b4451a0dde Update: 2026-07-23 20:50:10 2026-07-23 20:50:10 +03:00
Hamza-Ayed 92eb7fe25d Update: 2026-07-23 20:43:21 2026-07-23 20:43:21 +03:00
Hamza-Ayed 1e785061ec Fix decryptData null type error and live analytics overflow 2026-07-23 20:42:48 +03:00
Hamza-Ayed 12e70f3d7e Fix status warning in admin login 2026-07-23 20:34:27 +03:00
Hamza-Ayed 12a1cbc98c Update: 2026-07-23 20:15:22 2026-07-23 20:15:22 +03:00
Hamza-Ayed c35b350b31 Update: 2026-07-23 16:56:57 2026-07-23 16:56:58 +03:00
Hamza-Ayed 1cef598fc5 Update: 2026-07-23 02:19:01 2026-07-23 02:19:01 +03:00
Hamza-Ayed e5bf70fddb Update: 2026-07-23 02:14:58 2026-07-23 02:14:58 +03:00
Hamza-Ayed 9e065e5a83 Update: 2026-07-23 00:16:56 2026-07-23 00:16:56 +03:00
Hamza-Ayed ed8a93a6a3 Update: 2026-07-23 00:13:41 2026-07-23 00:13:41 +03:00
Hamza-Ayed a7fa40dc31 Update: 2026-07-23 00:11:36 2026-07-23 00:11:37 +03:00
Hamza-Ayed b86f95c90d Update: 2026-07-22 23:27:18 2026-07-22 23:27:18 +03:00
Hamza-Ayed b83ca1f664 Update: 2026-07-22 23:20:32 2026-07-22 23:20:33 +03:00
Hamza-Ayed fbb95c70fa Update: 2026-07-22 23:06:34 2026-07-22 23:06:34 +03:00
Hamza-Ayed b9efba3787 Update: 2026-07-22 18:32:11 2026-07-22 18:32:11 +03:00
Hamza-Ayed e798f84c03 Update: 2026-07-22 18:30:10 2026-07-22 18:30:11 +03:00
Hamza-Ayed 55fbe22f8e Update: 2026-07-22 18:23:10 2026-07-22 18:23:10 +03:00
Hamza-Ayed f76623a25e Update: 2026-07-22 18:20:33 2026-07-22 18:20:33 +03:00
Hamza-Ayed f62716b510 Update: 2026-07-22 18:11:54 2026-07-22 18:11:54 +03:00