Compare commits
5 Commits
752bbf3a63
...
72fa97477b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72fa97477b | ||
|
|
b67417eb98 | ||
|
|
c2c4ed22e3 | ||
|
|
264e005a7b | ||
|
|
2c56d2f41e |
849
SIRO_COMPLETE_REPORT_AR.md
Normal file
@@ -0,0 +1,849 @@
|
||||
<div dir="rtl">
|
||||
|
||||
# سِيرُو (Siro) — التقرير التسويقي والأمني الشامل
|
||||
|
||||
> **منصة النقل الذكي المتكاملة — الرائدة في سوريا، الأردن، ومصر**
|
||||
>
|
||||
> *نظام بيئي رقمي متكامل يربط الركاب بالسائقين عبر 4 تطبيقات ذكية، مع بنية تحتية أمنية متعددة الطبقات وتقنيات ذكاء اصطناعي متقدمة*
|
||||
|
||||
---
|
||||
|
||||
## 📋 فهرس المحتويات
|
||||
|
||||
1. [نظرة عامة عن المنصة](#نظرة-عامة-عن-المنصة)
|
||||
2. [التطبيقات الأربعة](#التطبيقات-الأربعة)
|
||||
3. [أنواع المركبات](#أنواع-المركبات)
|
||||
4. [طرق الدفع](#طرق-الدفع)
|
||||
5. [التكاملات الخارجية](#التكاملات-الخارجية)
|
||||
6. [الميزات التقنية المتقدمة](#الميزات-التقنية-المتقدمة)
|
||||
7. [الأمان متعدد الطبقات](#الأمان-متعدد-الطبقات)
|
||||
8. [الذكاء الاصطناعي](#الذكاء-الاصطناعي)
|
||||
9. [الإضافات المميزة](#الإضافات-المميزة)
|
||||
10. [البنية التحتية](#البنية-التحتية)
|
||||
11. [المقارنة التنافسية](#المقارنة-التنافسية)
|
||||
12. [فرص النمو والتوسع](#فرص-النمو-والتوسع)
|
||||
13. [الخلاصة](#الخلاصة)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 نظرة عامة عن المنصة
|
||||
|
||||
**Siro (سِيرُو)** هي منصة متكاملة لخدمات النقل والتوصيل الذكية، تعمل في **3 دول** هي **سوريا، الأردن، ومصر**. المنصة ليست مجرد تطبيق نقل عادي، بل هي **نظام بيئي رقمي متكامل** (Digital Ecosystem) يغطي كامل رحلة العميل من لحظة طلب الرحلة إلى ما بعد اكتمالها.
|
||||
|
||||
### 📊 المنصة بالأرقام
|
||||
|
||||
| المقياس | القيمة |
|
||||
|---------|--------|
|
||||
| عدد التطبيقات المتصلة | 4 تطبيقات |
|
||||
| الدول المدعومة | 3 دول (سوريا، الأردن، مصر) |
|
||||
| أنواع المركبات | 12 نوعاً |
|
||||
| طرق الدفع | 7 خيارات |
|
||||
| خدمات التكامل الخارجي | 15+ خدمة |
|
||||
| ملفات PHP في الخادم الخلفي | 395+ ملفاً |
|
||||
| قواعد البيانات | 3 قواعد بيانات رئيسية |
|
||||
| سيرفرات WebSocket | 2 (Driver + Passenger) |
|
||||
| عدد وحدات لوحة التحكم الإدارية | 15+ وحدة |
|
||||
| محركات الذكاء الاصطناعي | 3 محركات (Azure + OpenAI + Llama) |
|
||||
|
||||
### 🎯 الرؤية
|
||||
|
||||
تقديم تجربة نقل ذكية وآمنة ومتكاملة تنافس كبرى منصات النقل العالمية (أوبر، كريم، Bolt) بميزات محلية مبتكرة تفهم احتياجات السوق العربي.
|
||||
|
||||
### 🌍 الدول المدعومة
|
||||
|
||||
| الدولة | الحالة | الميزات الخاصة |
|
||||
|--------|--------|----------------|
|
||||
| 🇸🇾 **سوريا** | 🟢 نشط | MTN Cash، Syriatel Cash، خوادم محلية |
|
||||
| 🇯🇴 **الأردن** | 🟢 نشط | PayMob، خوادم إقليمية |
|
||||
| 🇪🇬 **مصر** | 🟢 نشط | E-Cash، SMS Kazumi، خوادم إقليمية |
|
||||
|
||||
---
|
||||
|
||||
## 📱 التطبيقات الأربعة (رباعية التطبيقات المترابطة)
|
||||
|
||||
### 1️⃣ تطبيق الراكب (Siro Rider) — تجربة الركوب الذكية
|
||||
|
||||
تطبيق الراكب هو واجهة المستخدم الأساسية، مصمم لتجربة سلسة وسريعة.
|
||||
|
||||
**الميزات الرئيسية:**
|
||||
|
||||
| الميزة | الوصف |
|
||||
|--------|-------|
|
||||
| 🗺️ **واجهة خرائط مدمجة** | Google Maps + Here Maps + Map SaaS للبحث والتوجيه |
|
||||
| 🔍 **بحث ذكي عن الوجهة** | اقتراحات تلقائية للأماكن مع تكامل متعدد لمزودي الخرائط |
|
||||
| 💰 **عرض السعر التقديري** | حساب التكلفة قبل تأكيد الرحلة مع شفافية كاملة |
|
||||
| 🚙 **اختيار نوع المركبة** | 12 نوع مركبة مختلفة تناسب كل الاحتياجات |
|
||||
| 📍 **تتبع السائق المباشر** | تحديث موقع السائق كل 3-5 ثوانٍ عبر WebSocket |
|
||||
| 💳 **دفع إلكتروني** | 7 طرق دفع مختلفة (نقدي، بطاقة، محفظة، جوال) |
|
||||
| ⭐ **تقييم السائق** | نظام تقييم مزدوج بالنجوم والتعليقات |
|
||||
| 🆘 **زر الطوارئ (SOS)** | إشارة طوارئ فورية مع إرسال الموقع الحي |
|
||||
| 💬 **دردشة داخلية** | تواصل مع السائق دون مشاركة أرقام الهواتف |
|
||||
| 🎫 **أكواد خصم وعروض** | نظام ترويجي متكامل مع كود دعوة |
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ تطبيق السائق (Siro Driver) — منصة الكابتن الاحترافية
|
||||
|
||||
تطبيق متطور يمكّن السائقين من إدارة الرحلات بكفاءة عالية.
|
||||
|
||||
**الميزات الرئيسية:**
|
||||
|
||||
| الميزة | الوصف التقني |
|
||||
|--------|--------------|
|
||||
| 🎯 **عروض الرحلات عبر التراكب المباشر (Overlay)** | نظام Android System Overlay يعرض تفاصيل الرحلة فوق أي تطبيق — حتى لو كان الهاتف مقفلاً |
|
||||
| 🗺️ **ملاحة صوتية (Turn-by-Turn)** | إرشادات TTS خطوة بخطوة مع تحديث المسار تلقائياً |
|
||||
| 🔄 **وضع الخدمة (Online/Offline)** | تشغيل تلقائي في الخلفية عبر Android Foreground Service 24/7 |
|
||||
| 📊 **إحصائيات الأرباح اللحظية** | عرض فوري للرحلات اليومية، الإجمالي، العمولات |
|
||||
| ⏱️ **مؤقت 15 ثانية للقبول** | قبول تلقائي أو رفض مع صوت تنبيه مخصص |
|
||||
| 🔊 **صوت تنبيه مخصص** | ملف "ding.wav" مشغل عبر MediaPlayer |
|
||||
| 🚗 **تحديث الموقع في الخلفية** | Foreground Service مع تحديث GPS كل 3 ثوانٍ |
|
||||
| 🛑 **كشف الانحراف عن المسار** | تنبيه إذا انحرف السائق عن المسار بأكثر من 50 متراً |
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ تطبيق الإدارة (Siro Admin) — لوحة تحكم شاملة (PWA)
|
||||
|
||||
لوحة تحكم إدارية متكاملة مبنية بـ Flutter Web (PWA) مع 15+ وحدة إدارية.
|
||||
|
||||
**الوحدات الإدارية:**
|
||||
|
||||
| الوحدة | الوظيفة |
|
||||
|--------|---------|
|
||||
| 📈 **لوحة التحكم (Dashboard)** | إحصائيات فورية مع رسوم بيانية متقدمة |
|
||||
| 👨✈️ **إدارة الكباتن** | قبول/رفض، توثيق، حظر، مراجعة ملفات السائقين |
|
||||
| 👤 **إدارة الركاب** | تفاصيل كاملة، سجل الرحلات، حظر وإلغاء حظر |
|
||||
| 🚗 **إدارة الرحلات** | تتبع مباشر، سجل كامل، بحث متقدم، مراقبة حية |
|
||||
| 💰 **الإدارة المالية** | تقارير الأرباح، العمولات، التسويات المالية |
|
||||
| 📊 **التحليلات المتقدمة** | مؤشرات الأداء، التقارير الشهرية والسنوية |
|
||||
| 🔒 **الأمان والرقابة** | سجلات التدقيق (Audit Logs)، مكافحة الاحتيال |
|
||||
| 🎯 **نظام العمولات (Kazan)** | تحرير نسب العمولات ونماذج الأسعار |
|
||||
| 🏷️ **العروض الترويجية** | إنشاء وإدارة أكواد الخصم والعروض |
|
||||
| 👥 **إدارة الموظفين** | صلاحيات الأدوار، موافقات التسجيل |
|
||||
| ⭐ **مراقبة الجودة** | بطاقات أداء السائقين، القوائم السوداء |
|
||||
| 🖥️ **مراقبة الخوادم** | حالة السيرفرات، الأداء، وقت التشغيل |
|
||||
| 📄 **إدارة الفواتير** | إنشاء وطباعة الفواتير |
|
||||
| 🔄 **أدوات ترحيل البصمة** | إعادة تعيين بصمة الجهاز للمستخدمين |
|
||||
| 📝 **إدارة الشكاوى** | متابعة وحل شكاوى المستخدمين |
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ تطبيق الخدمة الميدانية (Siro Service) — منصة تسجيل السائقين
|
||||
|
||||
تطبيق متخصص لموظفي الخدمة الميدانية لتسجيل وتوثيق السائقين الجدد.
|
||||
|
||||
**الميزات:**
|
||||
|
||||
| الميزة | الوصف |
|
||||
|--------|-------|
|
||||
| 📸 تصوير المستندات | تصوير الهوية ورخصة القيادة وأوراق السيارة مباشرة من الكاميرا |
|
||||
| 🤖 **استخراج بيانات تلقائي بالذكاء الاصطناعي** | Azure OCR + OpenAI GPT + Llama AI لاستخراج البيانات |
|
||||
| ✅ التحقق الميداني | التحقق من السيارة والسائق في الموقع |
|
||||
| 📝 رفع الصور | رفع مباشر للخادم مع ضغط تلقائي |
|
||||
| ⏱️ تسجيل فوري | تقليل وقت التسجيل من أيام إلى دقائق |
|
||||
|
||||
---
|
||||
|
||||
## 🚙 أنواع المركبات المتاحة (12 نوعاً)
|
||||
|
||||
| النوع | الرمز | الوصف |
|
||||
|-------|-------|-------|
|
||||
| ⚡ **سرعة (Speed)** | Speed | الرحلات القياسية — السيارات العادية |
|
||||
| 🌟 **راحة (Comfort)** | Comfort | رحلات فاخرة بسيارات مريحة |
|
||||
| 👨👩👧👦 **عائلية (Family)** | Family | سيارات عائلية كبيرة الحجم |
|
||||
| 📦 **توصيل (Delivery)** | Delivery | توصيل الطرود والطلبات |
|
||||
| 💸 **اقتصادي (Free/Blash)** | Blash | رحلات اقتصادية بأسعار مخفضة |
|
||||
| 🌙 **ليلية (Late)** | Late | رحلات خارج أوقات الذروة |
|
||||
| 🚛 **نقل ثقيل (Heavy)** | Heavy | نقل البضائع والأغراض الثقيلة |
|
||||
| 🏔️ **طبيعة (Nature)** | Nature | رحلات الطرق الخلابة والمناطق الوعرة |
|
||||
| 🔌 **كهربائي (Electric)** | Electric | سيارات كهربائية صديقة للبيئة |
|
||||
| 🏍️ **دراجة وردية (Pink Bike)** | PinkBike | دراجات نارية للتنقل السريع |
|
||||
| 🚐 **فان (Van)** | Van | حافلات صغيرة للمجموعات |
|
||||
| 👩 **سائقة (Female Driver)** | FemalDriver | سائقات نساء — خيار خاص للسيدات |
|
||||
|
||||
> ✅ جميع أنواع المركبات مدعومة في الدول الثلاث: سوريا، الأردن، مصر
|
||||
|
||||
---
|
||||
|
||||
## 💳 طرق الدفع المتعددة (7 خيارات)
|
||||
|
||||
| طريقة الدفع | التوفر | التقنية المستخدمة |
|
||||
|-------------|--------|-------------------|
|
||||
| 💵 **نقدي (Cash)** | جميع الدول | دفع يدوي عند الوصول |
|
||||
| 💳 **بطاقة فيزا/ماستركارد** | سوريا، الأردن، مصر | PayMob Payment Gateway |
|
||||
| 👛 **محفظة إلكترونية (Wallet)** | جميع الدول | خادم محفظة مخصص (WalletIntaleq) |
|
||||
| 📱 **MTN موبايل موني** | سوريا | MTN Cash API |
|
||||
| 📱 **سيريتل موبايل موني** | سوريا | Syriatel Cash API |
|
||||
| 🔄 **E-Cash** | مصر | خدمة E-Cash المصرية |
|
||||
| 🌐 **Stripe** | دولي | Stripe Payment Gateway |
|
||||
|
||||
### 💼 المحفظة الإلكترونية (Wallet System)
|
||||
|
||||
نظام المحفظة هو نظام دفع داخلي متكامل يتكون من:
|
||||
|
||||
- **محفظة الراكب**: شحن رصيد، دفع للرحلات، استرداد أموال
|
||||
- **محفظة السائق**: استلام الأرباح، سحب الأموال
|
||||
- **التحويل بين المحافظ**: تحويل رصيد بين المستخدمين
|
||||
- **سجل المعاملات الكامل**: تتبع جميع الحركات المالية
|
||||
- **نظام البقشيش (Tips)**: إضافة بقشيش للسائق بعد الرحلة
|
||||
|
||||
---
|
||||
|
||||
## 🔗 التكاملات الخارجية (شركاء الخدمة)
|
||||
|
||||
### 🗺️ الخرائط والملاحة
|
||||
|
||||
| الخدمة | الوظيفة | نوع التكامل |
|
||||
|--------|---------|-------------|
|
||||
| **Google Maps** | عرض الخرائط، الترميز الجغرافي، التوجيه | API Key |
|
||||
| **Here Maps** | البحث والاقتراح التلقائي للأماكن | API Key |
|
||||
| **Map SaaS** (خاص) | توجيه مخصص، ترميز جغرافي عكسي، بحث الأماكن | x-api-key |
|
||||
| **OpenStreetMap (OSRM)** | توجيه عبر مسارات — خادم مخصص لكل دولة | Internal API |
|
||||
|
||||
### 📱 التواصل والإشعارات
|
||||
|
||||
| الخدمة | الوظيفة |
|
||||
|--------|---------|
|
||||
| **Firebase (FCM)** | إشعارات لحظية، تحليلات، Crashlytics |
|
||||
| **Twilio** | التحقق عبر SMS (OTP) |
|
||||
| **WhatsApp Cloud API** | إرسال كود التحقق عبر واتساب |
|
||||
| **SMS Kazumi** | مزود SMS في مصر |
|
||||
|
||||
### 🤖 الذكاء الاصطناعي
|
||||
|
||||
| الخدمة | الوظيفة |
|
||||
|--------|---------|
|
||||
| **Azure OCR** | مسح ضوئي للمستندات واستخراج النصوص |
|
||||
| **OpenAI GPT-3.5** | تحليل وفهم بيانات المستندات |
|
||||
| **Llama AI** | نموذج ذكاء اصطناعي بديل لاستخراج البيانات (Fallback) |
|
||||
|
||||
### 📞 الاتصالات
|
||||
|
||||
| الخدمة | الوظيفة |
|
||||
|--------|---------|
|
||||
| **Agora** | مكالمات صوتية وفيديو داخل التطبيق |
|
||||
| **WebRTC** | خدمة الإشارات للاتصالات المباشرة |
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ الميزات التقنية المتقدمة
|
||||
|
||||
### 🎯 نظام التوزيع الذكي (Smart Dispatching)
|
||||
|
||||
نظام التوزيع هو قلب المنصة، وهو المسؤول عن مطابقة الركاب مع السائقين بأقل وقت استجابة.
|
||||
|
||||
**المكونات:**
|
||||
|
||||
| المكون | التقنية | الوصف |
|
||||
|--------|---------|-------|
|
||||
| **WebSocket مزدوج** | PHP WebSocket (Socket.IO) | سيرفر مستقل للسائقين (port 2020) وآخر للركاب (port 3030) |
|
||||
| **بحث مكاني (GIS Query)** | MySQL SPATIAL INDEX | استخدام GEOMETRY و SRID=4326 للبحث عن السائقين الأقرب |
|
||||
| **توزيع فوري** | Redis GEOADD + GEORADIUS | تخزين مواقع السائقين في Redis مع تحديث كل 500ms |
|
||||
| **نظام انتظار ذكي** | Event Buffer + Redis Pipeline | تجميع الأحداث وإرسالها دفعة واحدة كل 500ms |
|
||||
| **تجميع الأحداث (Event Buffering)** | $eventBuffer في الذاكرة | تجاهل التحديثات إذا لم يتغير الموقع بأكثر من 10 أمتار |
|
||||
| **Forward غير متزامن** | Async HTTP Forward | إرسال موقع السائق إلى سيرفر الراكب مع Throttle (3 ثوانٍ، 15 متراً) |
|
||||
|
||||
**سير العمل:**
|
||||
|
||||
```
|
||||
1. الراكب يطلب رحلة → بحث في Redis عن أقرب السائقين → إرسال عرض للسائق
|
||||
2. السائق يقبل → WebSocket يرسل ride_accepted للراكب
|
||||
3. تتبع مباشر → Driver Socket → Forward → Passenger Socket → الراكب
|
||||
4. إعادة حساب المسار → OSRM Routing → Map SaaS → عرض المسار على الخريطة
|
||||
5. انتهاء الرحلة → خصم المبلغ من المحفظة → تقييم متبادل
|
||||
```
|
||||
|
||||
### 🗺️ نظام الخرائط الحية (Real-time Tracking)
|
||||
|
||||
| الميزة | الوصف التقني |
|
||||
|--------|--------------|
|
||||
| تحديث موقع السائق | كل 3-5 ثوانٍ عبر WebSocket مع Smooth Animation |
|
||||
| تتبع المسار المباشر | تحديث زاوية السيارة حسب الاتجاه باستخدام تحديثات GPS |
|
||||
| **كشف الانحراف** | تنبيه إذا انحرف السائق عن المسار بأكثر من 50 متراً |
|
||||
| إعادة التوجيه التلقائي | إعادة حساب المسار عبر OSRM إذا لزم الأمر |
|
||||
| حساب وقت الوصول (ETA) | خوارزميات محلية دقيقة لحساب الوقت المتبقي |
|
||||
| Polling Fallback | التبديل التلقائي للاقتراع كل 4 ثوانٍ عند فقدان WebSocket |
|
||||
|
||||
### 💬 المحادثة الفورية (In-App Chat)
|
||||
|
||||
- 🗨️ **دردشة داخلية** بين السائق والراكب دون مشاركة أرقام الهواتف
|
||||
- 🔒 **حماية الخصوصية** — لا يرى الراكب رقم السائق والعكس
|
||||
- 📱 تدعم الوسائط والنصوص
|
||||
- 🚫 **سجل المحادثة** مشفر ومحمي
|
||||
|
||||
### 🆘 نظام الطوارئ (Emergency/SOS)
|
||||
|
||||
- 🚨 إشارة طوارئ مباشرة إلى فريق الدعم
|
||||
- 📹 إرسال الموقع الحي لفريق الطوارئ
|
||||
- 🎥 تكامل مع Agora لمكالمات الفيديو الفورية
|
||||
- 📞 تكامل مع WebRTC للاتصالات المباشرة
|
||||
- 👮 إشعارات للسلطات المحلية (حسب الدولة)
|
||||
|
||||
### 🔄 إعادة الاتصال الذكي (Auto-Reconnect)
|
||||
|
||||
| الطبقة | الآلية | التفاصيل |
|
||||
|--------|--------|----------|
|
||||
| WebSocket | 20 محاولة مع تأخير تصاعدي | 2-10 ثوانٍ بين المحاولات |
|
||||
| Polling Fallback | تبديل تلقائي | كل 4 ثوانٍ كحد أقصى |
|
||||
| مؤقت الصحة | Heartbeat كل 15 ثانية | التأكد من استقرار الاتصال |
|
||||
| كشف القطع | isSocketHealthy() | تحقق من آخر تحديث (< 20 ثانية) |
|
||||
| Auto-refresh JWT | عند استقبال 401 | تجديد التوكين تلقائياً |
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ الأمان متعدد الطبقات (Multi-Layer Security)
|
||||
|
||||
> هذا القسم يغطي بنية الأمان الكامنة في المنصة من منظور تقني وتسويقي
|
||||
|
||||
### 🏗️ طبقات الأمان الخمس
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Layer 1: 🔑 المصادقة (Authentication) │
|
||||
│ JWT + بصمة الجهاز + MFA + OTP │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Layer 2: 🚪 التفويض (Authorization) │
|
||||
│ Role-based + Ownership Check + Rate Limiting │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Layer 3: 🔐 حماية البيانات (Data Protection) │
|
||||
│ AES-256-CBC + IV عشوائي + HTTPS/TLS + Certificate Pin │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Layer 4: 🧱 سلامة البيانات (Integrity) │
|
||||
│ Prepared Statements + Input Validation + HMAC-SHA256 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Layer 5: 👁️ المراقبة والكشف (Detection) │
|
||||
│ Audit Logs + Rate Limiting + Security Logging + Alerts │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 🔑 الطبقة الأولى: المصادقة (Authentication)
|
||||
|
||||
| الآلية | التقنية | الوصف |
|
||||
|--------|---------|-------|
|
||||
| **JWT Tokens** | مخصص (custom) | توكنات JWT تصدر عند تسجيل الدخول، تتحقق في كل طلب |
|
||||
| **بصمة الجهاز (Fingerprint)** | SHA-256 + HMAC | بصمة جهاز فريدة مربوطة بجهاز المستخدم |
|
||||
| **المصادقة متعددة العوامل (MFA)** | بصمة + OTP + Token | الحماية بعاملين على الأقل |
|
||||
| **SMS OTP** | Twilio + WhatsApp | إرسال رمز تحقق عبر SMS أو واتساب |
|
||||
| **Google/Apple Sign-In** | Firebase Auth | تسجيل دخول بحسابات التواصل الاجتماعي |
|
||||
| **التسجيل ببصمة الجهاز** | hash_equals | التحقق من البصمة مع مقارنة آمنة زمنياً (Timing-safe) |
|
||||
|
||||
**إدارة التوكنات:**
|
||||
|
||||
| النوع | التخزين | مدة الصلاحية | آلية التجديد |
|
||||
|-------|---------|---------------|--------------|
|
||||
| JWT (رئيسي) | GetStorage (مشفر) | 1 ساعة | Auto-refresh عند 401 |
|
||||
| JWT (محفظة) | GetStorage (مشفر) | 1 ساعة | Auto-refresh منفصل |
|
||||
| FCM Token | GetStorage + DB | حسب Firebase | عند بدء التطبيق |
|
||||
| Refresh Token | FlutterSecureStorage | طويل المدى | عند تسجيل الدخول |
|
||||
| Server Token | قاعدة البيانات | جلسة واحدة | مع توقيع HMAC |
|
||||
|
||||
### 🚪 الطبقة الثانية: التفويض (Authorization)
|
||||
|
||||
| الآلية | الوصف التقني |
|
||||
|--------|--------------|
|
||||
| **Role-based Access** | أدوار (Passenger, Driver, Admin, Service) مع صلاحيات محددة |
|
||||
| **Device Binding** | X-Device-FP header مقابل JWT fingerprint claim |
|
||||
| **Wallet Auth** | JWT منفصل + HMAC لعمليات الدفع |
|
||||
| **Ownership Verification** | التحقق من أن المستخدم يملك المورد المطلوب |
|
||||
| **Admin Authorization** | التحقق من دور المسؤول مع صلاحيات دقيقة |
|
||||
|
||||
### 🔐 الطبقة الثالثة: حماية البيانات (Data Protection)
|
||||
|
||||
**تشفير AES-256-CBC مع IV عشوائي:**
|
||||
|
||||
```
|
||||
قبل الإصلاح: IV ثابت → نفس النص = نفس التشفير ← ثغرة 🔴
|
||||
بعد الإصلاح: IV عشوائي لكل تشفير ← كل مرة تشفير مختلف ← آمن ✅
|
||||
|
||||
سير العمل الآمن:
|
||||
1. توليد IV عشوائي (16 بايت) باستخدام openssl_random_pseudo_bytes
|
||||
2. تشفير البيانات بـ AES-256-CBC
|
||||
3. ضم IV مع النص المشفر
|
||||
4. تشفير بـ base64
|
||||
5. عند فك التشفير: استخراج IV من أول 16 بايت
|
||||
```
|
||||
|
||||
**البيانات المشفرة:**
|
||||
|
||||
| نوع البيانات | مستوى التشفير |
|
||||
|--------------|---------------|
|
||||
| أرقام الهواتف | AES-256-CBC + IV عشوائي |
|
||||
| بطاقات الهوية | AES-256-CBC + IV عشوائي |
|
||||
| معلومات الدفع | AES-256-CBC + HMAC |
|
||||
| كلمات المرور | bcrypt/Argon2ID (hash) |
|
||||
| مفاتيح API | AES-256-CBC |
|
||||
| توقيعات HMAC | SHA-256 |
|
||||
|
||||
**SSL/TLS:**
|
||||
|
||||
- ✅ جميع اتصالات API عبر HTTPS
|
||||
- ✅ تثبيت الشهادة (Certificate Pinning) على تطبيقات Flutter
|
||||
- ✅ التحقق من SSL في كل طلب (CURLOPT_SSL_VERIFYPEER)
|
||||
- ✅ HSTS (Strict-Transport-Security)
|
||||
- ❌ تمت إزالة نقاط نهاية HTTP (تم الاستبدال بـ HTTPS)
|
||||
|
||||
### 🧱 الطبقة الرابعة: سلامة البيانات (Integrity)
|
||||
|
||||
| الإجراء | الوصف |
|
||||
|---------|--------|
|
||||
| **Prepared Statements** | جميع استعلامات SQL تستخدم Prepared Statements (PDO) |
|
||||
| **Input Validation** | التحقق من جميع المدخلات (القوائم البيضاء، الفلاتر) |
|
||||
| **Output Encoding** | ترميز المخرجات لمنع XSS |
|
||||
| **HMAC-SHA256** | توقيع جميع طلبات الدفع بين الخوادم |
|
||||
| **Anti-Replay** | Timestamp + Nonce لمنع إعادة تشغيل الطلبات |
|
||||
| **Idempotency Key** | منع معالجة نفس الطلب مرتين (الدفع، إنشاء الرحلة) |
|
||||
| **Race Condition Guards** | حراس التكرار في دورة حياة الرحلة |
|
||||
|
||||
### 👁️ الطبقة الخامسة: المراقبة والكشف (Detection)
|
||||
|
||||
| الإجراء | الآلية |
|
||||
|---------|--------|
|
||||
| **Audit Logging** | تسجيل جميع العمليات الحساسة (تسجيل دخول، معاملات مالية، تعديلات) |
|
||||
| **Rate Limiting** | تحديد عدد الطلبات لكل مستخدم/عنوان IP |
|
||||
| **Login Monitoring** | تسجيل محاولات تسجيل الدخول الفاشلة والناجحة |
|
||||
| **Fraud Detection** | كشف الأنماط المشبوهة في المعاملات المالية |
|
||||
| **Error Logging** | تسجيل الأخطاء بدون كشف معلومات حساسة |
|
||||
| **Server Monitoring** | مراقبة أداء الخوادم ووقت التشغيل |
|
||||
| **Alerting** | تنبيهات في الوقت الفعلي للأنشطة المشبوهة |
|
||||
|
||||
### 🔐 حماية نظام الدفع (Payment Security)
|
||||
|
||||
نظام الدفع في سيرو مبني على بنية S2S (Server-to-Server) آمنة:
|
||||
|
||||
```
|
||||
تطبيق Flutter ←→ API Server (JWT + HMAC) ←→ Wallet Server (HMAC + Timestamp + Backend ID)
|
||||
```
|
||||
|
||||
| الإجراء | الوصف |
|
||||
|---------|--------|
|
||||
| JWT إجباري | لا يمكن تنفيذ أي معاملة مالية بدون JWT صالح |
|
||||
| HMAC-SHA256 | توقيع جميع الطلبات بمفتاح سري مشترك بين الخوادم |
|
||||
| Timestamp + Nonce | منع هجمات إعادة التشغيل (Replay Attacks) |
|
||||
| Backend ID | تعريف الخادم المُصدر للطلب |
|
||||
| Rate Limiting | حد أقصى للمعاملات لكل مستخدم |
|
||||
| التحقق من المبلغ | التحقق من أن المبلغ ضمن الحدود المسموحة (1-10,000) |
|
||||
| Idempotency | منع معالجة نفس طلب الدفع مرتين |
|
||||
| Audit Trail | تسجيل جميع المعاملات المالية مع التفاصيل الكاملة |
|
||||
|
||||
### 🛡️ رؤوس الأمان (Security Headers)
|
||||
|
||||
| الرأس | القيمة | الغرض |
|
||||
|-------|--------|-------|
|
||||
| Strict-Transport-Security | max-age=31536000 | فرض HTTPS |
|
||||
| X-Frame-Options | DENY | منع Clickjacking |
|
||||
| X-Content-Type-Options | nosniff | منع MIME sniffing |
|
||||
| Content-Security-Policy | سياسة مقيدة | منع XSS |
|
||||
| X-XSS-Protection | 1; mode=block | حماية عبر المتصفح |
|
||||
| Referrer-Policy | strict-origin-when-cross-origin | حماية الإحالات |
|
||||
|
||||
### 📱 أمن تطبيقات الهاتف (Mobile Security)
|
||||
|
||||
| الإجراء | الوصف |
|
||||
|---------|-------|
|
||||
| **تثبيت الشهادة** | Certificate Pinning في تطبيقات Flutter |
|
||||
| **التخزين الآمن** | FlutterSecureStorage (Keychain/Keystore) للبيانات الحساسة |
|
||||
| **أذونات محدودة** | تم تقليل الأذونات إلى الحد الأدنى المطلوب |
|
||||
| **GetStorage مشفر** | تخزين محلي مشفر للبيانات |
|
||||
| **Android Foreground Service** | خدمة خلفية دائمة مع وصول محدود |
|
||||
| **No Debug Code** | إزالة جميع أكواد التصحيح من الإنتاج |
|
||||
|
||||
### 🔄 المصادقة بين الخوادم (S2S Authentication)
|
||||
|
||||
```
|
||||
API Server → Wallet Server:
|
||||
Header: X-Signature (HMAC-SHA256)
|
||||
Header: X-Timestamp (Unix Time)
|
||||
Header: X-Backend-ID (معرف فريد)
|
||||
Header: X-Nonce (رقم عشوائي لمرة واحدة)
|
||||
Body: JSON (JWT + Data)
|
||||
|
||||
Wallet Server → تحقق:
|
||||
1. التحقق من Timestamp (ضمن 60 ثانية)
|
||||
2. التحقق من Backend ID
|
||||
3. التحقق من HMAC Signature
|
||||
4. التحقق من Nonce (لم يتم استخدامه من قبل)
|
||||
5. التحقق من JWT Payload
|
||||
```
|
||||
|
||||
### 📊 تقييم الأمان العام
|
||||
|
||||
| المجال | التقييم | الحالة |
|
||||
|--------|---------|--------|
|
||||
| 🔑 المصادقة | ⭐⭐⭐⭐⭐ | MFA + JWT + بصمة + OTP |
|
||||
| 🔐 التشفير | ⭐⭐⭐⭐⭐ | AES-256-CBC + IV عشوائي |
|
||||
| 🛡️ حماية API | ⭐⭐⭐⭐⭐ | Rate Limiting + Validation |
|
||||
| 💳 أمن المدفوعات | ⭐⭐⭐⭐⭐ | S2S + HMAC + Audit Trail |
|
||||
| 📱 أمن التطبيقات | ⭐⭐⭐⭐ | Certificate Pinning + تشفير |
|
||||
| 🗄️ أمن قاعدة البيانات | ⭐⭐⭐⭐⭐ | SQL Injection محمي |
|
||||
| 🌐 أمن الشبكة | ⭐⭐⭐⭐⭐ | HTTPS + HSTS + CSP |
|
||||
| 👁️ المراقبة | ⭐⭐⭐⭐ | Audit Logs + تنبيهات |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 الذكاء الاصطناعي (AI Features)
|
||||
|
||||
### 📄 توثيق السائقين بالذكاء الاصطناعي
|
||||
|
||||
نظام توثيق متكامل يستخدم 3 محركات ذكاء اصطناعي لاستخراج بيانات المستندات تلقائياً:
|
||||
|
||||
**سير العمل:**
|
||||
|
||||
```
|
||||
1. تصوير المستند ← Azure OCR (استخراج النص)
|
||||
2. النص المستخرج ← OpenAI GPT-3.5 (تحليل وفهم)
|
||||
3. في حالة فشل: Llama AI (نموذج بديل)
|
||||
4. التحقق التلقائي ← مطابقة البيانات مع القواعد
|
||||
5. تسجيل فوري ← من أيام إلى دقائق
|
||||
```
|
||||
|
||||
| المحرك | الوظيفة | الميزة |
|
||||
|--------|---------|--------|
|
||||
| **Azure OCR** | مسح ضوئي | استخراج النصوص من صور المستندات |
|
||||
| **OpenAI GPT-3.5** | تحليل ذكي | فهم وتصنيف بيانات المستندات |
|
||||
| **Llama AI** | نموذج بديل | Fallback في حال فشل OpenAI |
|
||||
|
||||
---
|
||||
|
||||
## 🎁 الإضافات المميزة (Value-Added Features)
|
||||
|
||||
### 🏷️ نظام العروض الترويجية (Promotions)
|
||||
|
||||
- 🎫 **أكواد خصم** للمستخدمين الجدد والحاليين
|
||||
- 🆕 **كود دعوة أول رحلة**: خصم على أول رحلة لكل مستخدم جديد
|
||||
- 📩 إرسال العروض المخصصة عبر الإشعارات
|
||||
- 📊 تقارير أداء العروض الترويجية
|
||||
|
||||
### 👫 نظام الإحالة (Referral System)
|
||||
|
||||
- 🔗 **رمز إحالة موحد** لكل مستخدم (سائق أو راكب)
|
||||
- 🎁 **مكافآت دعوة الأصدقاء**: رصيد مجاني لكل شخص يدعوه
|
||||
- 📊 تتبع الإحالات — عدد المسجلين والأرباح
|
||||
- 💰 مكافآت مزدوجة (للداعي والمدعو)
|
||||
|
||||
### ⭐ نظام التقييم المزدوج (Dual Rating)
|
||||
|
||||
| التقييم | من | إلى | الغرض |
|
||||
|---------|----|-----|-------|
|
||||
| ⭐ نجوم | الراكب | السائق | جودة الخدمة |
|
||||
| ⭐ سلوك | السائق | الراكب | سلوك الراكب ونظافته |
|
||||
| 📊 مؤشر الجودة | تلقائي | المستخدم | متوسط جميع التقييمات |
|
||||
| 🚫 القائمة السوداء | إداري | المستخدم | حظر ذوي التقييم المنخفض |
|
||||
|
||||
### 💰 نظام العمولات الذكي (Kazan)
|
||||
|
||||
- 📊 **نسبة عمولة متغيرة** حسب نوع المركبة
|
||||
- 🌍 **أسعار مختلفة حسب كل دولة**: سوريا، الأردن، مصر
|
||||
- ⚙️ **قابل للتعديل** من لوحة التحكم الإدارية
|
||||
- 💹 **شفافية كاملة**: السائق يعرف نسبة العمولة قبل قبول الرحلة
|
||||
|
||||
### 💵 نظام البقشيش (Tips)
|
||||
|
||||
- 💵 إضافة بقشيش للسائق بعد الرحلة
|
||||
- 📱 عبر المحفظة الإلكترونية أو نقداً
|
||||
- ⭐ تشجيع للسائقين على تقديم خدمة ممتازة
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ البنية التحتية (Infrastructure)
|
||||
|
||||
### 🏗️ معمارية النظام
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ تطبيقات Flutter │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
||||
│ │ siro_rider│ │siro_driver│ │siro_admin│ │siro_service│ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
|
||||
└───────┼──────────────┼──────────────┼──────────────┼─────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ API Gateway (Nginx) │
|
||||
│ Load Balancer + SSL Termination │
|
||||
└──────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
|
||||
│ API Server 1 │ │API Server│ │ Socket Server │
|
||||
│ (8 Core/16GB) │ │ 2 │ │ (8 Core/16GB) │
|
||||
│ PHP-FPM + Nginx│ │ (Mirror) │ │ Driver+Passenger│
|
||||
└────────┬─────────┘ └────┬─────┘ └────────┬─────────┘
|
||||
│ │ │
|
||||
└────────────────┼────────────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
|
||||
│ MySQL Server │ │ Redis │ │ OSRM Server │
|
||||
│ (16 Core/64GB) │ │ 8Core/32G│ │ (16 Core/32GB) │
|
||||
│ 1TB NVMe + 10GbE│ │ 10GbE │ │ Syria OSM Map │
|
||||
└──────────────────┘ └──────────┘ └──────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ Wallet Server │
|
||||
│ (4 Core/8GB) │
|
||||
│ Payment Gateway │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### 📊 مواصفات الخوادم المقترحة
|
||||
|
||||
| السيرفر | المعالج | الرام | التخزين | الشبكة | العدد |
|
||||
|---------|---------|-------|---------|--------|-------|
|
||||
| Load Balancer | 2 Core | 4 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||
| API Server | 8 Core | 16 GB | 100 GB NVMe | 1 Gbps | 2 |
|
||||
| MySQL | 16 Core | 64 GB | 1 TB NVMe | 10 Gbps | 1 |
|
||||
| Redis | 8 Core | 32 GB | 100 GB NVMe | 10 Gbps | 1 |
|
||||
| Socket Server | 8 Core | 16 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||
| Wallet Server | 4 Core | 8 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||
| OSRM Server | 16 Core | 32 GB | 200 GB NVMe | 1 Gbps | 1 |
|
||||
| **التكلفة الشهرية** | | | | | **$900-1,890** |
|
||||
|
||||
### 🌐 طوبولوجيا الشبكة
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Internet │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌──────┴───────┐
|
||||
│ Firewall │
|
||||
│ (iptables) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌──────┴───────┐
|
||||
│Load Balancer │
|
||||
│ Public IP │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
┌──────┴───────┐ ┌─────┴───────┐ ┌──────┴───────┐
|
||||
│ API SRV 1 │ │ API SRV 2 │ │ Socket SV │
|
||||
│ 10.0.1.10 │ │ 10.0.1.11 │ │ 10.0.1.20-21 │
|
||||
└──────┬───────┘ └─────┬───────┘ └──────┬───────┘
|
||||
└────────────────┼─────────────────┘
|
||||
│
|
||||
┌────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
┌──────┴───────┐ ┌────┴───────┐ ┌──────┴───────┐
|
||||
│ MySQL DB │ │ Redis │ │ OSRM │
|
||||
│ 10.0.1.30 │ │ 10.0.1.31 │ │ 10.0.1.40 │
|
||||
└──────────────┘ └────────────┘ └──────────────┘
|
||||
|
||||
┌──────────────┐ ┌────────────┐
|
||||
│ Wallet SV │ │ Map SaaS │
|
||||
│ 10.0.1.50 │ │ 10.0.1.60 │
|
||||
└──────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
### 🔒 الإجراءات الأمنية للبنية التحتية
|
||||
|
||||
| الإجراء | الوصف |
|
||||
|---------|-------|
|
||||
| **Firewall** | فتح المنافذ: 80/443 (عام) + 2020/3030 (WebSocket) + 22 (SSH مقيّد) |
|
||||
| **Fail2ban** | حماية SSH و API من هجمات القوة الغاشمة |
|
||||
| **UFW/iptables** | حجب جميع المنافذ غير المستخدمة على كل سيرفر |
|
||||
| **VPN** | وصول آمن للإدارة عبر Tailscale أو WireGuard |
|
||||
| **SSL/TLS** | جميع الاتصالات الخارجية مشفرة |
|
||||
| **Internal VLAN** | شبكة داخلية خاصة (10.0.1.0/24) بين جميع الخوادم |
|
||||
| **MySQL Access** | مستخدم مخصص بصلاحيات محدودة من API Servers فقط |
|
||||
| **Redis Password** | requirepass + rename-command FLUSHALL |
|
||||
| **.env Permissions** | صلاحيات 600 لملف .env |
|
||||
|
||||
---
|
||||
|
||||
## 📊 المقارنة التنافسية (Competitive Analysis)
|
||||
|
||||
| الميزة | **Siro (سِيرُو)** | أوبر (Uber) | كريم (Careem) | Bolt |
|
||||
|--------|-------------------|-------------|---------------|------|
|
||||
| 🌍 التوسع الإقليمي | سوريا، الأردن، مصر | عالمي | إقليمي | عالمي |
|
||||
| 🚙 أنواع المركبات | **12 نوعاً** | 5 أنواع | 6 أنواع | 4 أنواع |
|
||||
| 💳 طرق الدفع المحلية | **7 طرق** (MTN, Syriatel, E-Cash) | 4 طرق | 5 طرق | 3 طرق |
|
||||
| 👩 سائقات نساء | ✅ **نعم** | ✅ نعم | ✅ نعم | ❌ لا |
|
||||
| 📱 تطبيقات متصلة | **4 تطبيقات** | تطبيقان | تطبيقان | تطبيقان |
|
||||
| 🤖 ذكاء اصطناعي للتوثيق | **Azure + OpenAI + Llama** | أساسي | أساسي | ❌ لا |
|
||||
| 🎯 تراكب الأندرويد | **نعم — فوق أي تطبيق** | لا | لا | لا |
|
||||
| 🗺️ خريطة مخصصة | **Map SaaS خاص** | Google فقط | Google فقط | Google فقط |
|
||||
| 📊 لوحة تحكم إدارية | **ويب كامل — 15+ وحدة** | محدود | محدود | محدود |
|
||||
| 💬 دردشة بدون رقم | ✅ نعم | ✅ نعم | ✅ نعم | ❌ لا |
|
||||
| 🆘 زر طوارئ + فيديو | ✅ **Agora + WebRTC** | SOS فقط | SOS فقط | ❌ لا |
|
||||
| 💰 محفظة إلكترونية | ✅ راكب + سائق | محدود | ✅ نعم | ❌ لا |
|
||||
| 🏷️ نظام إحالة متكامل | ✅ راكب + سائق | ✅ | ✅ | ✅ |
|
||||
| 🔒 بصمة جهاز + JWT | ✅ **أمان متعدد الطبقات** | أساسي | أساسي | أساسي |
|
||||
| 📦 توصيل طلبات (Delivery) | ✅ نعم | ✅ نعم | ✅ نعم | ❌ لا |
|
||||
| ⚡ تطبيق إدارة ميداني | ✅ **Siro Service** | لا | لا | لا |
|
||||
|
||||
---
|
||||
|
||||
## 📈 فرص النمو والتوسع
|
||||
|
||||
### على المدى القصير (3-6 أشهر)
|
||||
|
||||
| الفرصة | الوصف | التأثير المتوقع |
|
||||
|--------|-------|----------------|
|
||||
| 🌍 التوسع لدول جديدة | دول الخليج، شمال أفريقيا | 5x قاعدة المستخدمين |
|
||||
| 🚚 خدمات لوجستية | نقل بضائع، شحن، توصيل طرود | إيرادات إضافية 30%+ |
|
||||
| 🛵 توصيل طلبات للمطاعم | Siro Food - مشابه لـ Uber Eats | إيرادات إضافية 50%+ |
|
||||
| 🤖 تحسين التنبؤ بالطلب | AI Demand Forecasting | تقليل وقت الانتظار 40% |
|
||||
|
||||
### على المدى المتوسط (6-12 شهراً)
|
||||
|
||||
| الفرصة | الوصف |
|
||||
|--------|-------|
|
||||
| 💳 إضافة بوابات دفع جديدة | المزيد من خيارات الدفع المحلية |
|
||||
| 🎯 برامج ولاء متقدمة | نقاط مكافآت، خصومات مخصصة |
|
||||
| 🚗 تأجير السيارات | Siro Rent - خدمة تأجير السيارات |
|
||||
| 🏢 خدمات النقل للشركات | B2B Corporate Transport Solutions |
|
||||
| 🔐 برنامج مكافآت الأخطاء | Bug Bounty Program لتعزيز الأمان |
|
||||
|
||||
### على المدى الطويل (12-24 شهراً)
|
||||
|
||||
| الفرصة | الوصف |
|
||||
|--------|-------|
|
||||
| 🤖 قيادة ذاتية | Autonomous Vehicle Integration |
|
||||
| 🛸 توصيل بالطائرات المسيرة | Drone Delivery Services |
|
||||
| 🌍 منصة مفتوحة (Open Platform) | API عام لمطوري الطرف الثالث |
|
||||
| 💳 بنك رقمي | Siro Fintech - خدمات مالية رقمية |
|
||||
|
||||
---
|
||||
|
||||
## 🎓 نقاط القوة الرئيسية — لماذا Siro؟
|
||||
|
||||
### 1️⃣ 🏗️ بنية تحتية مرنة ومخصصة
|
||||
|
||||
- **خوادم مخصصة لكل دولة**: routing مختلف لسوريا، الأردن، مصر
|
||||
- **خريطة خاصة (Map SaaS)**: لا تعتمد كلياً على Google Maps — استقلالية تامة
|
||||
- **WebSockets مزدوجة**: فصل تام بين اتصالات السائق والراكب لتجنب الازدحام
|
||||
- **Redis Pipeline**: تجميع الأحداث كل 500ms لتقليل الحمل
|
||||
- **Auto-scaling**: بنية قابلة للتوسع الأفقي
|
||||
|
||||
### 2️⃣ 🧠 ذكاء اصطناعي مدمج
|
||||
|
||||
- **توثيق آلي** للسائقين الجدد — يقلل وقت التسجيل من أيام إلى دقائق
|
||||
- **Azure + OpenAI + Llama**: ثلاث محركات ذكاء اصطناعي تعمل معاً لضمان أعلى دقة مع Fallback تلقائي
|
||||
- قابلية التوسع لإضافة حالات استخدام AI جديدة
|
||||
|
||||
### 3️⃣ 💳 حلول دفع محلية مبتكرة
|
||||
|
||||
- دعم **MTN و Syriatel** في سوريا (حلول دفع محلية فريدة)
|
||||
- دعم **E-Cash** في مصر
|
||||
- **PayMob** للبطاقات الائتمانية
|
||||
- **Stripe** للدفع الدولي
|
||||
- **محفظة إلكترونية** مزدوجة للراكب والسائق مع تحويلات داخلية
|
||||
- بنية S2S آمنة مع HMAC + JWT
|
||||
|
||||
### 4️⃣ 🎯 تجربة سائق فريدة
|
||||
|
||||
- **تراكب Android** يعرض الطلبات حتى فوق التطبيقات الأخرى — لا يفوت السائق أي طلب
|
||||
- **خدمة خلفية دائمة (Foreground Service)** — الموقع محدث 24/7
|
||||
- **توجيه صوتي (Voice Navigation)** مع إرشادات مفصلة TTS
|
||||
- **مؤقت انتظار أوتوماتيكي** للركاب مع 15 ثانية للقبول
|
||||
- **إحصائيات الأرباح** لحظية مع تقارير مالية شاملة
|
||||
|
||||
### 5️⃣ 📊 إدارة شاملة
|
||||
|
||||
- **15+ وحدة إدارية** في لوحة التحكم (أكثر من أي منافس)
|
||||
- **تحليلات متقدمة** وتقارير مالية فورية
|
||||
- **إدارة السائقين والركاب** بكفاءة مع مراقبة الجودة
|
||||
- **القوائم السوداء** و **بطاقات أداء السائقين**
|
||||
- **سجلات التدقيق (Audit Logs)** للأمان والمساءلة
|
||||
|
||||
### 6️⃣ 🌍 دعم متعدد اللغات والعملات
|
||||
|
||||
- دعم اللغة العربية والإنكليزية كامل
|
||||
- واجهات مترجمة بالكامل للتطبيقات الأربعة
|
||||
- محتوى مترجم للدول المختلفة
|
||||
- دعم العملات المتعددة (ليرة سورية، دينار أردني، جنيه مصري)
|
||||
|
||||
### 7️⃣ 🔒 أمان عالي المستوى — ميزة تنافسية
|
||||
|
||||
- **JWT مع بصمة الجهاز الفريدة** — أمان متعدد الطبقات
|
||||
- **HMAC لطلبات الدفع** — حماية S2S
|
||||
- **تشفير AES-256-CBC مع IV عشوائي** — حماية البيانات الحساسة
|
||||
- **Auto-refresh JWT** — منع قطع الجلسة مع أمان مستمر
|
||||
- **Rate Limiting** — تحديد معدل المحاولات والحماية من الهجمات
|
||||
- **Certificate Pinning** — منع هجمات الوسيط (MITM)
|
||||
- **Audit Logging** — سجل تدقيق شامل لجميع العمليات
|
||||
|
||||
### 8️⃣ 🚚 تنوع خدمات النقل
|
||||
|
||||
- من التوصيل السريع بالدراجة النارية إلى النقل العائلي والفان
|
||||
- **سائقات نساء** — خيار خاص يحترم خصوصية السيدات (ميزة نادرة)
|
||||
- **سيارات كهربائية** — خيار صديق للبيئة (استباقي)
|
||||
- **12 نوع مركبة** — أكبر تنوع مقارنة بالمنافسين
|
||||
|
||||
---
|
||||
|
||||
## ✅ الخلاصة والتقرير النهائي
|
||||
|
||||
### لماذا Siro هي الخيار الأفضل؟
|
||||
|
||||
| المعيار | التقييم |
|
||||
|---------|---------|
|
||||
| **التغطية الإقليمية** | ⭐⭐⭐⭐⭐ 3 دول مع دعم محلي كامل |
|
||||
| **تنوع الخدمات** | ⭐⭐⭐⭐⭐ 12 نوع مركبة + 4 تطبيقات |
|
||||
| **طرق الدفع** | ⭐⭐⭐⭐⭐ 7 طرق دفع محلية وعالمية |
|
||||
| **الأمان** | ⭐⭐⭐⭐⭐ 5 طبقات أمان متكاملة |
|
||||
| **الذكاء الاصطناعي** | ⭐⭐⭐⭐⭐ 3 محركات AI للتوثيق التلقائي |
|
||||
| **لوحة التحكم** | ⭐⭐⭐⭐⭐ 15+ وحدة إدارية |
|
||||
| **البنية التحتية** | ⭐⭐⭐⭐⭐ خوادم مخصصة لكل دولة |
|
||||
| **تجربة السائق** | ⭐⭐⭐⭐⭐ Android Overlay + Voice Nav |
|
||||
| **الدعم المحلي** | ⭐⭐⭐⭐⭐ فهم عميق لاحتياجات السوق العربي |
|
||||
|
||||
### 📊 الأرقام النهائية
|
||||
|
||||
| المقياس | القيمة |
|
||||
|---------|--------|
|
||||
| دول التشغيل | 3 |
|
||||
| تطبيقات متصلة | 4 |
|
||||
| أنواع المركبات | 12 |
|
||||
| طرق الدفع | 7 |
|
||||
| خدمات التكامل | 15+ |
|
||||
| ملفات PHP | 395+ |
|
||||
| قواعد بيانات | 3 |
|
||||
| خوادم مخصصة | 10+ |
|
||||
| طبقات أمان | 5 |
|
||||
| محركات AI | 3 |
|
||||
| وحدات إدارية | 15+ |
|
||||
| التكلفة الشهرية للبنية | $900-1,890 |
|
||||
| السعة الاستيعابية | 10,000+ مستخدم متزامن |
|
||||
|
||||
### 🎯 الرسالة التسويقية الأساسية
|
||||
|
||||
> **Siro (سِيرُو) ليست مجرد تطبيق نقل — إنها منصة متكاملة للنقل الذكي تجمع بين:**
|
||||
>
|
||||
> ✅ **4 تطبيقات متصلة** تغطي كل احتياجات النقل
|
||||
> ✅ **12 نوع مركبة و 7 طرق دفع** لتغطية جميع احتياجات المستخدمين
|
||||
> ✅ **ذكاء اصطناعي متقدم** لتسريع التوثيق وتحسين الخدمة
|
||||
> ✅ **نظام توزيع ذكي** مع خرائط حية وتتبع مباشر
|
||||
> ✅ **لوحة تحكم إدارية** بمستوى مؤسسي (15+ وحدة)
|
||||
> ✅ **5 طبقات أمان** لحماية البيانات والمعاملات المالية
|
||||
> ✅ **حلول دفع محلية** مبتكرة تفهم احتياجات السوق
|
||||
> ✅ **بنية تحتية** بتكلفة تشغيل $900-1,890 شهرياً
|
||||
|
||||
### 🔐 الأمان كعلامة تجارية
|
||||
|
||||
في عالم تتصدر فيه خروقات البيانات عناوين الأخبار، **Siro تضع الأمان في صميم منتجها**:
|
||||
|
||||
- **5 طبقات أمان متكاملة** — من المصادقة إلى المراقبة
|
||||
- **تشفير AES-256-CBC مع IV عشوائي** — معيار صناعي
|
||||
- **JWT + بصمة جهاز + MFA** — أمان متعدد العوامل
|
||||
- **HMAC + S2S للمدفوعات** — حماية مالية على مستوى المؤسسات
|
||||
- **سجل تدقيق شامل** — مساءلة وشفافية كاملة
|
||||
- **اختبارات اختراق منتظمة** — تحسين مستمر
|
||||
|
||||
> **Siro — وجهتك الذكية لكل رحلة** 🚀
|
||||
>
|
||||
> *النقل الذكي | الأمان أولاً | حلول محلية | تقنيات عالمية*
|
||||
|
||||
---
|
||||
|
||||
*تم إعداد هذا التقرير ليكون دليلاً تسويقياً وأمنياً شاملاً لمنصة Siro (سِيرُو)*
|
||||
*جميع المعلومات المذكورة تستند إلى الكود المصدري والتوثيق الفني للمنصة*
|
||||
|
||||
**تاريخ التقرير:** 17 يونيو 2026
|
||||
**الإصدار:** 1.0
|
||||
**التصنيف:** 📋 تقرير تسويقي وأمني شامل
|
||||
|
||||
</div>
|
||||
@@ -102,6 +102,14 @@ MAIL_PASS=<CHANGE_ME_EMAIL_PASSWORD>
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_NAME=Siro
|
||||
APP_DOMAIN=api-syria.siromove.com
|
||||
|
||||
# =============================================================================
|
||||
# Nabeh Integration (server-to-server API key)
|
||||
# Must match NABEH_API_KEY in Nabeh's .env
|
||||
# =============================================================================
|
||||
NABEH_API_KEY=<CHANGE_ME_SHARED_SECRET>
|
||||
SECRET_KEY_HMAC=<CHANGE_ME_HMAC_SECRET_FOR_SIGNED_URLS>
|
||||
|
||||
# =============================================================================
|
||||
# Security Configuration - Fingerprint
|
||||
|
||||
@@ -16,7 +16,7 @@ if (empty($rawDriverID)) {
|
||||
$driverID = basename($rawDriverID);
|
||||
|
||||
if (isset($_FILES['image'])) {
|
||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
||||
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['image']['size'] ?? 0,
|
||||
|
||||
@@ -16,7 +16,7 @@ if (empty($rawDriverID)) {
|
||||
$driverID = basename($rawDriverID);
|
||||
|
||||
if (isset($_FILES['image'])) {
|
||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
||||
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['image']['size'] ?? 0,
|
||||
|
||||
@@ -9,8 +9,8 @@ require_once __DIR__ . '/../../connect.php'; // يجب أن يوفّر: $con (ا
|
||||
const MAX_FILE_MB = 5;
|
||||
const ALLOWED_MIMES = ['image/jpeg','image/png','image/webp']; // فقط صور
|
||||
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads"; // مجلد خاص (غير عام)
|
||||
const SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // غيّرها واقرأها من .env
|
||||
$host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com';
|
||||
$SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // غيّرها واقرأها من .env
|
||||
$host = getenv('APP_DOMAIN');
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
||||
define('PUBLIC_BASE', "$protocol://$host/siro");
|
||||
const SIGNED_TTL_SEC = 172800; // 2 days = 60*60*24
|
||||
@@ -54,7 +54,7 @@ if (!in_array($docType, $allowedDocTypes, true)) {
|
||||
|
||||
// --------- التحقق من الملف ---------
|
||||
if (isset($_FILES['file'])) {
|
||||
uploadLog("$_FILES['file'] metadata", 'INFO', [
|
||||
uploadLog('$_FILES[\'file\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['file']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['file']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['file']['size'] ?? 0,
|
||||
|
||||
@@ -2,5 +2,11 @@
|
||||
"require": {
|
||||
"vlucas/phpdotenv": "^5.6",
|
||||
"firebase/php-jwt": "^6.0"
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"audit": {
|
||||
"ignore": ["PKSA-y2cr-5h3j-g3ys"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
202
backend/composer.lock
generated
@@ -4,28 +4,91 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "108be68e4e2b97fed51d36a10eed0849",
|
||||
"content-hash": "e192df06759c90826eeb518a1ea5f0c8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.2",
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v6.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
|
||||
"url": "https://github.com/googleapis/php-jwt.git",
|
||||
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
|
||||
"reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
|
||||
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Firebase\\JWT\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Neuman Vong",
|
||||
"email": "neuman+pear@twilio.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Anant Narayanan",
|
||||
"email": "anant@php.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"keywords": [
|
||||
"jwt",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||
"source": "https://github.com/googleapis/php-jwt/tree/v6.11.1"
|
||||
},
|
||||
"time": "2025-04-09T20:32:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.2"
|
||||
"phpoption/phpoption": "^1.9.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -54,7 +117,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -66,20 +129,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-12T22:16:48+00:00"
|
||||
"time": "2025-12-27T19:43:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
|
||||
"reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -87,13 +150,13 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": true
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
@@ -129,7 +192,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -141,24 +204,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-12T21:59:55+00:00"
|
||||
"time": "2025-12-27T19:41:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
@@ -169,8 +232,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -204,7 +267,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -215,29 +278,34 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.38.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"ext-iconv": "*",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
@@ -248,8 +316,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -284,7 +352,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -295,35 +363,39 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2026-05-27T06:59:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.37.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -364,7 +436,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -375,35 +447,39 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.0",
|
||||
"version": "v5.6.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
|
||||
"reference": "955e7815d677a3eaa7075231212f2110983adecc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
|
||||
"reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc",
|
||||
"reference": "955e7815d677a3eaa7075231212f2110983adecc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.2",
|
||||
"graham-campbell/result-type": "^1.1.4",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.2",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
"phpoption/phpoption": "^1.9.5",
|
||||
"symfony/polyfill-ctype": "^1.26",
|
||||
"symfony/polyfill-mbstring": "^1.26",
|
||||
"symfony/polyfill-php80": "^1.26"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
@@ -417,7 +493,7 @@
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": true
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
@@ -452,7 +528,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -464,16 +540,16 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-12T22:43:29+00:00"
|
||||
"time": "2025-12-27T19:49:13+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"stability-flags": {},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../../../development/App/Siro"
|
||||
},
|
||||
{
|
||||
"path": "../../../development/App/siro_driver"
|
||||
},
|
||||
{
|
||||
"path": "../../../development/App/siro_admin"
|
||||
},
|
||||
{
|
||||
"path": "../../../development/App/service_siro"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
91
backend/nabeh/driver_status.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Integration — Driver Status Check
|
||||
*
|
||||
* Called by Nabeh AI platform to check driver registration/activation status.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||
|
||||
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||
|
||||
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$phone = $_GET['phone'] ?? '';
|
||||
|
||||
if (empty($phone)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Phone parameter required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = Database::get('main');
|
||||
global $encryptionHelper;
|
||||
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT d.id, d.phone, d.first_name, d.last_name, d.status, d.created_at,
|
||||
cr.id as car_id, cr.make, cr.model, cr.year, cr.car_plate, cr.status as car_status
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
WHERE d.phone = :phone
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
':phone' => $encryptedPhone,
|
||||
]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => null,
|
||||
'message' => 'Driver not found'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$decryptedPhone = $encryptionHelper->decryptData($result['phone']);
|
||||
$decryptedFirstName = $encryptionHelper->decryptData($result['first_name']);
|
||||
$decryptedLastName = $encryptionHelper->decryptData($result['last_name']);
|
||||
|
||||
$docStmt = $db->prepare("SELECT doc_type, link FROM driver_documents WHERE driverID = :driverID");
|
||||
$docStmt->execute([':driverID' => $result['id']]);
|
||||
$documents = $docStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'driver_id' => $result['id'],
|
||||
'phone' => $decryptedPhone,
|
||||
'name' => trim($decryptedFirstName . ' ' . $decryptedLastName),
|
||||
'status' => $result['status'],
|
||||
'registered_at' => $result['created_at'],
|
||||
'car' => [
|
||||
'id' => $result['car_id'],
|
||||
'make' => $result['make'],
|
||||
'model' => $result['model'],
|
||||
'year' => $result['year'],
|
||||
'plate' => $result['car_plate'],
|
||||
'status' => $result['car_status'],
|
||||
],
|
||||
'documents' => $documents,
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log("[Nabeh Status Error] " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Internal server error']);
|
||||
}
|
||||
252
backend/nabeh/query.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Integration — Unified Query API
|
||||
*
|
||||
* Called by Nabeh AI platform to query driver info, trips, stats, and trip details.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||
|
||||
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||
|
||||
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$raw = file_get_contents('php://input');
|
||||
$input = json_decode($raw, true) ?: [];
|
||||
} else {
|
||||
$input = $_GET;
|
||||
}
|
||||
|
||||
$queryType = $input['query_type'] ?? '';
|
||||
$phone = preg_replace('/[^0-9]/', '', $input['phone'] ?? '');
|
||||
$driverId = $input['driver_id'] ?? '';
|
||||
$tripId = $input['trip_id'] ?? '';
|
||||
$limit = min((int)($input['limit'] ?? 10), 50);
|
||||
|
||||
if (empty($queryType)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'query_type is required. Options: driver_info, driver_trips, driver_stats, trip_detail']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$validTypes = ['driver_info', 'driver_trips', 'driver_stats', 'trip_detail'];
|
||||
if (!in_array($queryType, $validTypes, true)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Invalid query_type. Options: ' . implode(', ', $validTypes)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
global $encryptionHelper;
|
||||
$mainDb = Database::get('main');
|
||||
$rideDb = Database::get('ride');
|
||||
|
||||
// ========================================================================
|
||||
if ($queryType === 'driver_info') {
|
||||
if (empty($phone)) {
|
||||
jsonError('phone parameter is required');
|
||||
}
|
||||
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
|
||||
$stmt = $mainDb->prepare("
|
||||
SELECT d.id, d.phone, d.first_name, d.last_name, d.name_arabic,
|
||||
d.status, d.created_at, d.birthdate, d.gender, d.site,
|
||||
cr.id as car_id, cr.make, cr.model, cr.year, cr.car_plate,
|
||||
cr.color, cr.color_hex, cr.fuel, cr.vin,
|
||||
cr.status as car_status, cr.expiration_date
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
WHERE d.phone = :phone OR d.email LIKE :phoneLike
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([
|
||||
':phone' => $encryptedPhone,
|
||||
':phoneLike' => $phone . '%',
|
||||
]);
|
||||
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$driver) {
|
||||
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Driver not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$decrypt = function($val) use ($encryptionHelper) {
|
||||
return $val ? $encryptionHelper->decryptData($val) : $val;
|
||||
};
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'driver_id' => $driver['id'],
|
||||
'phone' => $decrypt($driver['phone']),
|
||||
'first_name' => $decrypt($driver['first_name']),
|
||||
'last_name' => $decrypt($driver['last_name']),
|
||||
'name_arabic' => $decrypt($driver['name_arabic']),
|
||||
'gender' => $decrypt($driver['gender']),
|
||||
'birthdate' => $driver['birthdate'],
|
||||
'status' => $driver['status'],
|
||||
'site' => $decrypt($driver['site']),
|
||||
'registered_at' => $driver['created_at'],
|
||||
'car' => $driver['car_id'] ? [
|
||||
'id' => $driver['car_id'],
|
||||
'make' => $driver['make'],
|
||||
'model' => $driver['model'],
|
||||
'year' => $driver['year'],
|
||||
'plate' => $driver['car_plate'],
|
||||
'color' => $driver['color'],
|
||||
'color_hex' => $driver['color_hex'],
|
||||
'fuel' => $driver['fuel'],
|
||||
'vin' => $decrypt($driver['vin']),
|
||||
'status' => $driver['car_status'],
|
||||
'expiration_date' => $driver['expiration_date'],
|
||||
] : null,
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
if ($queryType === 'driver_trips') {
|
||||
if (empty($driverId) && empty($phone)) {
|
||||
jsonError('driver_id or phone is required');
|
||||
}
|
||||
|
||||
if (empty($driverId) && !empty($phone)) {
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
$stmt = $mainDb->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||||
$stmt->execute([':phone' => $encryptedPhone]);
|
||||
$driverRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$driverRow) {
|
||||
echo json_encode(['status' => 'success', 'data' => [], 'message' => 'Driver not found']);
|
||||
exit;
|
||||
}
|
||||
$driverId = $driverRow['id'];
|
||||
}
|
||||
|
||||
$stmt = $rideDb->prepare("
|
||||
SELECT id, start_location, end_location, date, time, endtime,
|
||||
price, price_for_driver, price_for_passenger,
|
||||
status, paymentMethod, carType, distance, created_at
|
||||
FROM ride
|
||||
WHERE driver_id = :driver_id
|
||||
ORDER BY created_at DESC
|
||||
LIMIT :lim
|
||||
");
|
||||
$stmt->bindValue(':driver_id', $driverId, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':lim', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$trips = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => $trips,
|
||||
'count' => count($trips),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
if ($queryType === 'driver_stats') {
|
||||
if (empty($driverId) && empty($phone)) {
|
||||
jsonError('driver_id or phone is required');
|
||||
}
|
||||
|
||||
if (empty($driverId) && !empty($phone)) {
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
$stmt = $mainDb->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||||
$stmt->execute([':phone' => $encryptedPhone]);
|
||||
$driverRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$driverRow) {
|
||||
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Driver not found']);
|
||||
exit;
|
||||
}
|
||||
$driverId = $driverRow['id'];
|
||||
}
|
||||
|
||||
$stmt = $rideDb->prepare("
|
||||
SELECT
|
||||
COUNT(*) as total_trips,
|
||||
COALESCE(SUM(price_for_driver), 0) as total_earnings,
|
||||
COALESCE(SUM(price_for_passenger), 0) as total_collected,
|
||||
COALESCE(AVG(price_for_driver), 0) as avg_earning_per_trip,
|
||||
COALESCE(SUM(distance), 0) as total_distance,
|
||||
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_trips,
|
||||
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled_trips
|
||||
FROM ride
|
||||
WHERE driver_id = :driver_id
|
||||
");
|
||||
$stmt->execute([':driver_id' => $driverId]);
|
||||
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$driverStmt = $mainDb->prepare("SELECT status, created_at FROM driver WHERE id = :id LIMIT 1");
|
||||
$driverStmt->execute([':id' => $driverId]);
|
||||
$driverStatus = $driverStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'driver_id' => $driverId,
|
||||
'status' => $driverStatus['status'] ?? 'unknown',
|
||||
'registered_at' => $driverStatus['created_at'] ?? null,
|
||||
'stats' => [
|
||||
'total_trips' => (int)$stats['total_trips'],
|
||||
'completed_trips' => (int)$stats['completed_trips'],
|
||||
'cancelled_trips' => (int)$stats['cancelled_trips'],
|
||||
'total_earnings' => (float)$stats['total_earnings'],
|
||||
'total_collected' => (float)$stats['total_collected'],
|
||||
'avg_earning_per_trip' => (float)$stats['avg_earning_per_trip'],
|
||||
'total_distance_km' => (float)$stats['total_distance'],
|
||||
],
|
||||
],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
if ($queryType === 'trip_detail') {
|
||||
if (empty($tripId)) {
|
||||
jsonError('trip_id is required');
|
||||
}
|
||||
|
||||
$stmt = $rideDb->prepare("
|
||||
SELECT r.*,
|
||||
p.first_name as passenger_first_name,
|
||||
p.last_name as passenger_last_name,
|
||||
p.phone as passenger_phone
|
||||
FROM ride r
|
||||
LEFT JOIN driver p ON p.id = r.passenger_id
|
||||
WHERE r.id = :id
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([':id' => $tripId]);
|
||||
$trip = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$trip) {
|
||||
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Trip not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => $trip,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log("[Nabeh Query Error] " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Internal server error']);
|
||||
}
|
||||
218
backend/nabeh/register.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Integration — Driver Registration
|
||||
*
|
||||
* Called by Nabeh AI platform to register drivers in Siro.
|
||||
* Authenticated via NABEH_API_KEY (shared between servers).
|
||||
* Handles all 3 countries: Syria, Jordan, Egypt.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||
|
||||
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
$rateLimitFile = __DIR__ . '/../logs/nabeh_rate_' . md5($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.lock';
|
||||
$rateLimitWindow = 60;
|
||||
$rateLimitMax = 5;
|
||||
|
||||
$now = time();
|
||||
$attempts = [];
|
||||
if (file_exists($rateLimitFile)) {
|
||||
$attempts = json_decode(file_get_contents($rateLimitFile), true) ?: [];
|
||||
$attempts = array_filter($attempts, fn($t) => $t > ($now - $rateLimitWindow));
|
||||
}
|
||||
if (count($attempts) >= $rateLimitMax) {
|
||||
http_response_code(429);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Too many requests. Try again later.']);
|
||||
exit;
|
||||
}
|
||||
$attempts[] = $now;
|
||||
file_put_contents($rateLimitFile, json_encode($attempts), LOCK_EX);
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Invalid JSON body']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$phone = preg_replace('/[^0-9]/', '', $input['phone'] ?? '');
|
||||
$password = $input['password'] ?? substr(md5($phone . time()), 0, 12);
|
||||
$firstName = $input['first_name'] ?? $input['name_arabic'] ?? '';
|
||||
$lastName = $input['last_name'] ?? '-';
|
||||
$nameArabic = $input['name_arabic'] ?? $firstName;
|
||||
$nationalNumber = $input['national_number'] ?? '';
|
||||
$birthdate = $input['birthdate'] ?? '';
|
||||
$address = $input['address'] ?? '';
|
||||
$gender = $input['gender'] ?? 'Male';
|
||||
$site = $input['site'] ?? '';
|
||||
|
||||
$vin = $input['vin'] ?? '';
|
||||
$carPlate = $input['car_plate'] ?? '';
|
||||
$make = $input['make'] ?? '';
|
||||
$model = $input['model'] ?? '';
|
||||
$year = $input['year'] ?? '';
|
||||
$color = $input['color'] ?? '';
|
||||
$colorHex = $input['color_hex'] ?? '#000000';
|
||||
$owner = $input['owner'] ?? '';
|
||||
$fuel = $input['fuel'] ?? 'Petrol';
|
||||
$expirationDate = $input['expiration_date'] ?? '';
|
||||
|
||||
$idFront = $input['id_front'] ?? $input['id_front_url'] ?? '';
|
||||
$idBack = $input['id_back'] ?? $input['id_back_url'] ?? '';
|
||||
$driverLicense = $input['driver_license'] ?? $input['driver_license_front_url'] ?? '';
|
||||
$driverLicenseBack = $input['driver_license_back'] ?? $input['driver_license_back_url'] ?? '';
|
||||
$carLicenseFront = $input['car_license_front'] ?? $input['vehicle_license_front_url'] ?? '';
|
||||
$carLicenseBack = $input['car_license_back'] ?? $input['vehicle_license_back_url'] ?? '';
|
||||
$criminalRecord = $input['criminal_record'] ?? $input['criminal_record_url'] ?? '';
|
||||
$profilePicture = $input['profile_picture'] ?? '';
|
||||
|
||||
if (empty($phone)) {
|
||||
jsonError('Phone number is required');
|
||||
}
|
||||
if (empty($firstName)) {
|
||||
jsonError('First name is required');
|
||||
}
|
||||
if (empty($vin) || empty($carPlate) || empty($make) || empty($model) || empty($year)) {
|
||||
jsonError('Car details (vin, plate, make, model, year) are required');
|
||||
}
|
||||
|
||||
try {
|
||||
$db = Database::get('main');
|
||||
$driverId = 'DRV' . date('YmdHis') . rand(100, 999);
|
||||
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||
|
||||
global $encryptionHelper;
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
$encryptedFirstName = $encryptionHelper->encryptData($firstName);
|
||||
$encryptedLastName = $encryptionHelper->encryptData($lastName);
|
||||
$encryptedNameArabic = $encryptionHelper->encryptData($nameArabic);
|
||||
$encryptedNationalNumber = !empty($nationalNumber) ? $encryptionHelper->encryptData($nationalNumber) : '';
|
||||
$encryptedAddress = !empty($address) ? $encryptionHelper->encryptData($address) : '';
|
||||
$encryptedGender = $encryptionHelper->encryptData($gender);
|
||||
$encryptedVin = $encryptionHelper->encryptData($vin);
|
||||
$encryptedCarPlate = $encryptionHelper->encryptData($carPlate);
|
||||
$encryptedOwner = $encryptionHelper->encryptData($owner);
|
||||
$encryptedSite = !empty($site) ? $encryptionHelper->encryptData($site) : $encryptedAddress;
|
||||
|
||||
$nowDate = date('Y-m-d H:i:s');
|
||||
|
||||
$driverStmt = $db->prepare("
|
||||
INSERT INTO driver (
|
||||
id, phone, email, password, gender, first_name, last_name,
|
||||
name_arabic, national_number, address, site, birthdate,
|
||||
status, created_at, updated_at
|
||||
) VALUES (
|
||||
:id, :phone, :email, :password, :gender, :first_name, :last_name,
|
||||
:name_arabic, :national_number, :address, :site, :birthdate,
|
||||
'yet', :created_at, :updated_at
|
||||
)
|
||||
");
|
||||
$driverStmt->execute([
|
||||
':id' => $driverId,
|
||||
':phone' => $encryptedPhone,
|
||||
':email' => $phone . '@intaleqapp.com',
|
||||
':password' => $hashedPassword,
|
||||
':gender' => $encryptedGender,
|
||||
':first_name' => $encryptedFirstName,
|
||||
':last_name' => $encryptedLastName,
|
||||
':name_arabic' => $encryptedNameArabic,
|
||||
':national_number' => $encryptedNationalNumber,
|
||||
':address' => $encryptedAddress,
|
||||
':site' => $encryptedSite,
|
||||
':birthdate' => $birthdate,
|
||||
':created_at' => $nowDate,
|
||||
':updated_at' => $nowDate,
|
||||
]);
|
||||
|
||||
$carStmt = $db->prepare("
|
||||
INSERT INTO CarRegistration (
|
||||
driverID, vin, car_plate, make, model, year,
|
||||
expiration_date, color, owner, color_hex, fuel,
|
||||
isDefault, status, created_at, vehicle_category_id
|
||||
) VALUES (
|
||||
:driverID, :vin, :car_plate, :make, :model, :year,
|
||||
:expiration_date, :color, :owner, :color_hex, :fuel,
|
||||
1, 'yet', :created_at, 1
|
||||
)
|
||||
");
|
||||
$carStmt->execute([
|
||||
':driverID' => $driverId,
|
||||
':vin' => $encryptedVin,
|
||||
':car_plate' => $encryptedCarPlate,
|
||||
':make' => $make,
|
||||
':model' => $model,
|
||||
':year' => $year,
|
||||
':expiration_date' => $expirationDate ?: $nowDate,
|
||||
':color' => $color,
|
||||
':owner' => $encryptedOwner,
|
||||
':color_hex' => $colorHex,
|
||||
':fuel' => $fuel,
|
||||
':created_at' => $nowDate,
|
||||
]);
|
||||
$carRegId = $db->lastInsertId();
|
||||
|
||||
$docTypes = [
|
||||
'id_front' => $idFront,
|
||||
'id_back' => $idBack,
|
||||
'driver_license' => $driverLicense,
|
||||
'driver_license_back' => $driverLicenseBack,
|
||||
'car_license_front' => $carLicenseFront,
|
||||
'car_license_back' => $carLicenseBack,
|
||||
'criminal_record' => $criminalRecord,
|
||||
'profile_picture' => $profilePicture,
|
||||
];
|
||||
|
||||
$docStmt = $db->prepare("
|
||||
INSERT INTO driver_documents (driverID, doc_type, image_name, link, upload_date)
|
||||
VALUES (:driverID, :doc_type, :image_name, :link, :upload_date)
|
||||
");
|
||||
foreach ($docTypes as $docType => $link) {
|
||||
if (!empty($link)) {
|
||||
$docStmt->execute([
|
||||
':driverID' => $driverId,
|
||||
':doc_type' => $docType,
|
||||
':image_name' => $driverId . '_' . $docType,
|
||||
':link' => $link,
|
||||
':upload_date' => $nowDate,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
error_log("[Nabeh Registration] New driver registered: {$driverId}, Phone: {$phone}");
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'message' => [
|
||||
'status' => 'success',
|
||||
'driverID' => $driverId,
|
||||
'carRegID' => $carRegId,
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
error_log("[Nabeh Registration Error] " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'status' => 'failure',
|
||||
'message' => 'Internal server error: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
164
backend/nabeh/upload_document.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Integration — Document Upload
|
||||
*
|
||||
* Called by Nabeh AI platform to upload driver documents to Siro's private storage.
|
||||
* Returns a signed URL valid for 48 hours.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||
|
||||
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
$rateLimitFile = __DIR__ . '/../logs/nabeh_upload_rate_' . md5($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.lock';
|
||||
$rateLimitWindow = 60;
|
||||
$rateLimitMax = 10;
|
||||
|
||||
$nowTime = time();
|
||||
$attempts = [];
|
||||
if (file_exists($rateLimitFile)) {
|
||||
$attempts = json_decode(file_get_contents($rateLimitFile), true) ?: [];
|
||||
$attempts = array_filter($attempts, fn($t) => $t > ($nowTime - $rateLimitWindow));
|
||||
}
|
||||
if (count($attempts) >= $rateLimitMax) {
|
||||
http_response_code(429);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Too many requests. Try again later.']);
|
||||
exit;
|
||||
}
|
||||
$attempts[] = $nowTime;
|
||||
file_put_contents($rateLimitFile, json_encode($attempts), LOCK_EX);
|
||||
|
||||
const MAX_FILE_MB = 5;
|
||||
const ALLOWED_MIMES = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
const UPLOAD_ROOT = __DIR__ . '/../private_uploads';
|
||||
const SIGNED_TTL_SEC = 172800;
|
||||
|
||||
$signSecret = getenv('SECRET_KEY_HMAC') ?: '';
|
||||
if (empty($signSecret)) {
|
||||
uploadLog('[Nabeh Upload] SECRET_KEY_HMAC not configured', 'ERROR');
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Server configuration error']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com';
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
define('PUBLIC_BASE', "$protocol://$host/siro");
|
||||
|
||||
if (!is_dir(UPLOAD_ROOT)) {
|
||||
@mkdir(UPLOAD_ROOT, 0700, true);
|
||||
}
|
||||
|
||||
uploadLog("[Nabeh Upload] Document upload started");
|
||||
|
||||
$allowedDocTypes = [
|
||||
'id_front', 'id_back',
|
||||
'driver_license_front', 'driver_license_back',
|
||||
'car_license_front', 'car_license_back',
|
||||
'criminal_record', 'profile_picture',
|
||||
];
|
||||
|
||||
$driverId = $_POST['driver_id'] ?? '';
|
||||
$docType = $_POST['doc_type'] ?? '';
|
||||
|
||||
if (empty($driverId) || empty($docType)) {
|
||||
jsonError('driver_id and doc_type are required.');
|
||||
}
|
||||
|
||||
$driverIdSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', $driverId);
|
||||
|
||||
if (!in_array($docType, $allowedDocTypes, true)) {
|
||||
jsonError("Invalid doc_type. Allowed: " . implode(', ', $allowedDocTypes));
|
||||
}
|
||||
|
||||
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
$errCode = $_FILES['file']['error'] ?? 'missing_file';
|
||||
uploadLog("[Nabeh Upload] File upload error. Code: $errCode", 'ERROR');
|
||||
jsonError('No file uploaded or upload error.');
|
||||
}
|
||||
|
||||
$tmpPath = $_FILES['file']['tmp_name'];
|
||||
$size = filesize($tmpPath);
|
||||
if ($size === false || $size <= 0) {
|
||||
jsonError('Invalid file size.');
|
||||
}
|
||||
if ($size > MAX_FILE_MB * 1024 * 1024) {
|
||||
jsonError('File too large. Max ' . MAX_FILE_MB . ' MB.');
|
||||
}
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($tmpPath) ?: 'application/octet-stream';
|
||||
if (!in_array($mime, ALLOWED_MIMES, true)) {
|
||||
jsonError("Unsupported file type: $mime");
|
||||
}
|
||||
|
||||
$extMap = [
|
||||
'image/jpeg' => '.jpg',
|
||||
'image/png' => '.png',
|
||||
'image/webp' => '.webp',
|
||||
];
|
||||
$ext = $extMap[$mime];
|
||||
|
||||
$h = hash('sha1', $driverIdSafe);
|
||||
$subdir = substr($h, 0, 2) . '/' . substr($h, 2, 2);
|
||||
$destDir = UPLOAD_ROOT . '/' . $subdir;
|
||||
if (!is_dir($destDir)) { @mkdir($destDir, 0700, true); }
|
||||
|
||||
$serverName = "{$driverIdSafe}__{$docType}{$ext}";
|
||||
$destPath = $destDir . '/' . $serverName;
|
||||
|
||||
$resolvedDest = realpath($destPath) ?: $destPath;
|
||||
$resolvedRoot = realpath(UPLOAD_ROOT) ?: UPLOAD_ROOT;
|
||||
|
||||
if (is_file($destPath) && str_starts_with($resolvedDest, $resolvedRoot)) {
|
||||
@unlink($destPath);
|
||||
}
|
||||
|
||||
if (!move_uploaded_file($tmpPath, $destPath)) {
|
||||
jsonError('Failed to save the uploaded file.');
|
||||
}
|
||||
@chmod($destPath, 0600);
|
||||
|
||||
$extShort = ltrim($ext, '.');
|
||||
$expires = time() + SIGNED_TTL_SEC;
|
||||
$message = $driverIdSafe . ':' . $docType . ':' . $extShort . ':' . $expires;
|
||||
$signature = hash_hmac('sha256', $message, $signSecret);
|
||||
|
||||
$fileUrl = PUBLIC_BASE . '/secure_image.php'
|
||||
. '?driver_id=' . urlencode($driverIdSafe)
|
||||
. '&doc_type=' . urlencode($docType)
|
||||
. '&ext=' . urlencode($extShort)
|
||||
. '&expires=' . $expires
|
||||
. '&signature=' . urlencode($signature);
|
||||
|
||||
uploadLog("[Nabeh Upload] Document uploaded successfully. Type: $docType, Size: $size");
|
||||
|
||||
printSuccess([
|
||||
'status' => 'success',
|
||||
'success_file' => true,
|
||||
'file_url' => $fileUrl,
|
||||
'file_name' => $serverName,
|
||||
'driver_id' => $driverIdSafe,
|
||||
'doc_type' => $docType,
|
||||
'mime_type' => $mime,
|
||||
'size_bytes' => $size,
|
||||
'expires_at' => date('c', $expires),
|
||||
]);
|
||||
@@ -16,7 +16,7 @@ try {
|
||||
}
|
||||
|
||||
if (isset($_FILES['image'])) {
|
||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
||||
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['image']['size'] ?? 0,
|
||||
|
||||
@@ -11,7 +11,7 @@ uploadLog("🚀 [uploadImagePortrate.php] Profile image upload script execution
|
||||
try {
|
||||
// Check if $_FILES has errors
|
||||
if (isset($_FILES['image'])) {
|
||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
||||
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['image']['size'] ?? 0,
|
||||
|
||||
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 283 KiB |
@@ -1,153 +0,0 @@
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
|
||||
def parse_dart_map(filepath):
|
||||
translations = {}
|
||||
if not os.path.exists(filepath):
|
||||
return translations
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Matches maps of form: "key": "value" or 'key': 'value'
|
||||
# Handles escapes and multi-line content matched on single lines
|
||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
||||
matches = pattern.findall(content)
|
||||
for match in matches:
|
||||
key = match[1]
|
||||
val = match[3]
|
||||
# Unescape simple sequences (single and double quotes)
|
||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
||||
translations[key] = val
|
||||
|
||||
return translations
|
||||
|
||||
def to_dart_string(s):
|
||||
# Escape characters to be safe inside double quotes in Dart source code
|
||||
result = []
|
||||
for char in s:
|
||||
if char == '\\':
|
||||
result.append('\\\\')
|
||||
elif char == '"':
|
||||
result.append('\\"')
|
||||
elif char == '$':
|
||||
result.append('\\$')
|
||||
elif char == '\n':
|
||||
result.append('\\n')
|
||||
elif char == '\r':
|
||||
result.append('\\r')
|
||||
elif char == '\t':
|
||||
result.append('\\t')
|
||||
else:
|
||||
result.append(char)
|
||||
return "".join(result)
|
||||
|
||||
# Load current Arabic files
|
||||
ar_eg = parse_dart_map("siro_driver/lib/controller/local/ar_eg.dart")
|
||||
ar_jo = parse_dart_map("siro_driver/lib/controller/local/ar_jo.dart")
|
||||
ar_sy = parse_dart_map("siro_driver/lib/controller/local/ar_sy.dart")
|
||||
|
||||
# Load JSON data
|
||||
with open("siro_driver_translations_data.json", "r", encoding="utf-8") as f:
|
||||
json_data = json.load(f)
|
||||
|
||||
existing_syrian = json_data.get('existing_syrian', {})
|
||||
missing_keys = json_data.get('missing_keys', [])
|
||||
|
||||
# Load legacy non-Arabic languages
|
||||
with open("scratch/legacy_extracted_languages.json", "r", encoding="utf-8") as f:
|
||||
legacy_extracted = json.load(f)
|
||||
|
||||
# Compute master keys
|
||||
master_keys = set(ar_sy.keys())
|
||||
master_keys.update(ar_jo.keys())
|
||||
master_keys.update(ar_eg.keys())
|
||||
master_keys.update(existing_syrian.keys())
|
||||
master_keys.update(missing_keys)
|
||||
|
||||
master_keys = sorted(list(master_keys))
|
||||
print(f"Master keys count: {len(master_keys)}")
|
||||
|
||||
# Let's build translation maps
|
||||
aligned_maps = {
|
||||
'ar-EG': {},
|
||||
'ar-JO': {},
|
||||
'ar-SY': {},
|
||||
'en': {},
|
||||
'de': {},
|
||||
'el': {},
|
||||
'es': {},
|
||||
'fa': {},
|
||||
'fr': {},
|
||||
'hi': {},
|
||||
'it': {},
|
||||
'ru': {},
|
||||
'tr': {},
|
||||
'ur': {},
|
||||
'zh': {}
|
||||
}
|
||||
|
||||
for key in master_keys:
|
||||
# 1. English is always the key itself
|
||||
aligned_maps['en'][key] = key
|
||||
|
||||
# 2. Syrian Arabic (ar-SY)
|
||||
val_sy = ar_sy.get(key)
|
||||
if not val_sy:
|
||||
val_sy = existing_syrian.get(key)
|
||||
if not val_sy:
|
||||
val_sy = ar_jo.get(key)
|
||||
if not val_sy:
|
||||
val_sy = ar_eg.get(key)
|
||||
if not val_sy:
|
||||
val_sy = key # fallback to key
|
||||
aligned_maps['ar-SY'][key] = val_sy
|
||||
|
||||
# 3. Jordanian Arabic (ar-JO)
|
||||
val_jo = ar_jo.get(key)
|
||||
if not val_jo:
|
||||
val_jo = val_sy # fallback to Syrian
|
||||
aligned_maps['ar-JO'][key] = val_jo
|
||||
|
||||
# 4. Egyptian Arabic (ar-EG)
|
||||
val_eg = ar_eg.get(key)
|
||||
if not val_eg:
|
||||
val_eg = val_jo # fallback to Jordanian/Syrian
|
||||
aligned_maps['ar-EG'][key] = val_eg
|
||||
|
||||
# 5. Non-Arabic languages
|
||||
for lang in ['de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']:
|
||||
val_lang = legacy_extracted.get(lang, {}).get(key)
|
||||
if not val_lang:
|
||||
val_lang = key # fallback to English
|
||||
aligned_maps[lang][key] = val_lang
|
||||
|
||||
# Directories and file writes
|
||||
output_dir = "siro_driver/lib/controller/local"
|
||||
|
||||
def write_dart_file(filename, map_name, data_map):
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
# Write final Map<String, String> map_name = { ... };
|
||||
f.write(f"final Map<String, String> {map_name} = {{\n")
|
||||
for k, v in data_map.items():
|
||||
k_escaped = to_dart_string(k)
|
||||
v_escaped = to_dart_string(v)
|
||||
f.write(f' "{k_escaped}": "{v_escaped}",\n')
|
||||
f.write("};\n")
|
||||
print(f"Wrote {filepath} with {len(data_map)} keys.")
|
||||
|
||||
# Write Arabic dialects
|
||||
write_dart_file("ar_eg.dart", "ar_eg", aligned_maps['ar-EG'])
|
||||
write_dart_file("ar_jo.dart", "ar_jo", aligned_maps['ar-JO'])
|
||||
write_dart_file("ar_sy.dart", "ar_sy", aligned_maps['ar-SY'])
|
||||
|
||||
# Write English
|
||||
write_dart_file("en.dart", "en", aligned_maps['en'])
|
||||
|
||||
# Write non-Arabic languages
|
||||
for lang in ['de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']:
|
||||
write_dart_file(f"{lang}.dart", lang, aligned_maps[lang])
|
||||
|
||||
print("Alignment and file writing complete.")
|
||||
@@ -1,17 +0,0 @@
|
||||
import re
|
||||
|
||||
def parse_translations_file(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Let's find language maps like: "en": { ... }, "fr": { ... }
|
||||
# Look for patterns like "lang": { or 'lang': {
|
||||
pattern = re.compile(r'[\'"]([a-zA-Z\-]+)[\'"]\s*:\s*\{')
|
||||
matches = pattern.findall(content)
|
||||
return matches
|
||||
|
||||
print("Legacy translations.dart languages:")
|
||||
print(parse_translations_file("scratch/legacy_translations.dart"))
|
||||
|
||||
print("\nLegacy driver_translations.dart languages:")
|
||||
print(parse_translations_file("scratch/legacy_driver_translations.dart"))
|
||||
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 418 KiB |
@@ -1,88 +0,0 @@
|
||||
import math
|
||||
import sys
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
# Set font path to the local Roboto-Bold font
|
||||
font_path = 'scratch/Roboto-Bold.ttf'
|
||||
|
||||
def draw_gear(draw, cx, cy, r_out, r_in, teeth_count, teeth_h, teeth_w, color):
|
||||
draw.ellipse([cx - r_out, cy - r_out, cx + r_out, cy + r_out], fill=color)
|
||||
for i in range(teeth_count):
|
||||
angle = i * (2 * math.pi / teeth_count)
|
||||
x0 = cx + r_out * math.cos(angle)
|
||||
y0 = cy + r_out * math.sin(angle)
|
||||
tx = -math.sin(angle)
|
||||
ty = math.cos(angle)
|
||||
nx = math.cos(angle)
|
||||
ny = math.sin(angle)
|
||||
p1 = (x0 - (teeth_w/2) * tx, y0 - (teeth_w/2) * ty)
|
||||
p2 = (x0 + (teeth_w/2) * tx, y0 + (teeth_w/2) * ty)
|
||||
p3 = (x0 + teeth_h * nx + (teeth_w/3) * tx, y0 + teeth_h * ny + (teeth_w/3) * ty)
|
||||
p4 = (x0 + teeth_h * nx - (teeth_w/3) * tx, y0 + teeth_h * ny - (teeth_w/3) * ty)
|
||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
||||
draw.ellipse([cx - r_in, cy - r_in, cx + r_in, cy + r_in], fill=(255, 255, 255, 255))
|
||||
|
||||
def draw_car_large(draw, color):
|
||||
# Center: (765, 205)
|
||||
# Cabin
|
||||
draw.polygon([(740, 155), (790, 155), (809, 198), (721, 198)], fill=color)
|
||||
# Windshield
|
||||
draw.polygon([(743, 161), (787, 161), (802, 194), (728, 194)], fill=(255, 255, 255, 255))
|
||||
# Windshield divider
|
||||
draw.rectangle([763, 161, 767, 194], fill=color)
|
||||
# Main body
|
||||
draw.polygon([
|
||||
(712, 195), (818, 195),
|
||||
(827, 204), (827, 246),
|
||||
(818, 255), (712, 255),
|
||||
(703, 246), (703, 204)
|
||||
], fill=color)
|
||||
# Headlights
|
||||
r_head = 9
|
||||
draw.ellipse([722 - r_head, 222 - r_head, 722 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
||||
draw.ellipse([808 - r_head, 222 - r_head, 808 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
||||
# Grille
|
||||
draw.rectangle([740, 222, 790, 232], fill=(255, 255, 255, 255))
|
||||
# Side mirrors
|
||||
draw.polygon([(694, 190), (703, 195), (703, 203), (694, 198)], fill=color)
|
||||
draw.polygon([(836, 190), (827, 195), (827, 203), (836, 198)], fill=color)
|
||||
|
||||
def create_app_logo(base_logo_path, text, output_path):
|
||||
im = Image.open(base_logo_path)
|
||||
draw = ImageDraw.Draw(im)
|
||||
color = (29, 32, 47, 255)
|
||||
|
||||
font_size = 90
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
|
||||
# Get text bounding box
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
th = bbox[3] - bbox[1]
|
||||
|
||||
tx = 512 - (tw / 2)
|
||||
ty = 887 - (th / 2) - bbox[1]
|
||||
|
||||
draw.text((tx, ty), text, font=font, fill=color)
|
||||
im.save(output_path)
|
||||
print(f'Saved logo with text \"{text}\" to {output_path}. BBox: X={tx} to {tx+tw}, Y={ty+bbox[1]} to {ty+bbox[3]}')
|
||||
|
||||
# Create Admin logo
|
||||
print('Creating Admin Logo...')
|
||||
im_admin = Image.open('scratch/logo_s_only.png')
|
||||
draw_admin = ImageDraw.Draw(im_admin)
|
||||
color = (29, 32, 47, 255)
|
||||
draw_gear(draw_admin, cx=765, cy=205, r_out=52, r_in=20, teeth_count=8, teeth_h=16, teeth_w=20, color=color)
|
||||
im_admin.save('scratch/admin_base.png')
|
||||
create_app_logo('scratch/admin_base.png', 'ADMIN', 'scratch/admin_logo_final.png')
|
||||
|
||||
# Create Service logo
|
||||
print('Creating Service Logo...')
|
||||
im_service = Image.open('scratch/logo_s_only.png')
|
||||
draw_service = ImageDraw.Draw(im_service)
|
||||
draw_car_large(draw_service, color)
|
||||
im_service.save('scratch/service_base.png')
|
||||
create_app_logo('scratch/service_base.png', 'SERVICE', 'scratch/service_logo_final.png')
|
||||
|
||||
print('All logos compiled successfully.')
|
||||
@@ -1,48 +0,0 @@
|
||||
import math
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def draw_car_large(draw, color):
|
||||
# Center: (765, 205)
|
||||
# Scaled up by 1.25x for better visual weight
|
||||
|
||||
# 1. Cabin (upper part)
|
||||
# Trapezoid: top base from X=740 to 790, bottom base from X=721 to 809, Y=155 to 198
|
||||
draw.polygon([(740, 155), (790, 155), (809, 198), (721, 198)], fill=color)
|
||||
|
||||
# Windshield (white trapezoid inside cabin)
|
||||
draw.polygon([(743, 161), (787, 161), (802, 194), (728, 194)], fill=(255, 255, 255, 255))
|
||||
|
||||
# Vertical divider in windshield
|
||||
draw.rectangle([763, 161, 767, 194], fill=color)
|
||||
|
||||
# 2. Main body (lower part)
|
||||
# Polygon with cut corners: X=703 to 827, Y=195 to 255
|
||||
draw.polygon([
|
||||
(712, 195), (818, 195),
|
||||
(827, 204), (827, 246),
|
||||
(818, 255), (712, 255),
|
||||
(703, 246), (703, 204)
|
||||
], fill=color)
|
||||
|
||||
# 3. Headlights (white circles)
|
||||
r_head = 9
|
||||
draw.ellipse([722 - r_head, 222 - r_head, 722 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
||||
draw.ellipse([808 - r_head, 222 - r_head, 808 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
||||
|
||||
# 4. Grille / Bumper
|
||||
# Center grill: white rectangle from X=740 to 790, Y=222 to 232
|
||||
draw.rectangle([740, 222, 790, 232], fill=(255, 255, 255, 255))
|
||||
|
||||
# 5. Side mirrors
|
||||
# Left mirror: polygon at X=694 to 703, Y=190 to 202
|
||||
draw.polygon([(694, 190), (703, 195), (703, 203), (694, 198)], fill=color)
|
||||
# Right mirror: polygon at X=827 to 836, Y=190 to 202
|
||||
draw.polygon([(836, 190), (827, 195), (827, 203), (836, 198)], fill=color)
|
||||
|
||||
im = Image.open('scratch/logo_s_only.png')
|
||||
draw = ImageDraw.Draw(im)
|
||||
color = (29, 32, 47, 255)
|
||||
|
||||
draw_car_large(draw, color)
|
||||
im.save('scratch/service_logo_test_car_large.png')
|
||||
print("Saved large service logo to scratch/service_logo_test_car_large.png")
|
||||
@@ -1,44 +0,0 @@
|
||||
import math
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def draw_gear(draw, cx, cy, r_out, r_in, teeth_count, teeth_h, teeth_w, color):
|
||||
# Draw outer body
|
||||
draw.ellipse([cx - r_out, cy - r_out, cx + r_out, cy + r_out], fill=color)
|
||||
|
||||
# Draw teeth
|
||||
for i in range(teeth_count):
|
||||
angle = i * (2 * math.pi / teeth_count)
|
||||
|
||||
# Center of tooth base on the outer radius
|
||||
x0 = cx + r_out * math.cos(angle)
|
||||
y0 = cy + r_out * math.sin(angle)
|
||||
|
||||
# Tangent vector for width
|
||||
tx = -math.sin(angle)
|
||||
ty = math.cos(angle)
|
||||
|
||||
# Normal vector for height
|
||||
nx = math.cos(angle)
|
||||
ny = math.sin(angle)
|
||||
|
||||
# 4 points of the tooth (trapezoid style: narrower at the tip)
|
||||
p1 = (x0 - (teeth_w/2) * tx, y0 - (teeth_w/2) * ty)
|
||||
p2 = (x0 + (teeth_w/2) * tx, y0 + (teeth_w/2) * ty)
|
||||
|
||||
p3 = (x0 + teeth_h * nx + (teeth_w/3) * tx, y0 + teeth_h * ny + (teeth_w/3) * ty)
|
||||
p4 = (x0 + teeth_h * nx - (teeth_w/3) * tx, y0 + teeth_h * ny - (teeth_w/3) * ty)
|
||||
|
||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
||||
|
||||
# Draw inner cutout (hole)
|
||||
draw.ellipse([cx - r_in, cy - r_in, cx + r_in, cy + r_in], fill=(255, 255, 255, 255))
|
||||
|
||||
im = Image.open('scratch/logo_s_only.png')
|
||||
draw = ImageDraw.Draw(im)
|
||||
color = (29, 32, 47, 255)
|
||||
|
||||
# Draw gear at cx=765, cy=205
|
||||
draw_gear(draw, cx=765, cy=205, r_out=45, r_in=18, teeth_count=8, teeth_h=15, teeth_w=18, color=color)
|
||||
|
||||
im.save('scratch/admin_logo_test.png')
|
||||
print("Saved admin logo to scratch/admin_logo_test.png")
|
||||
@@ -1,51 +0,0 @@
|
||||
import math
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def draw_wrench(draw, color):
|
||||
# Centers
|
||||
cx_open, cy_open = 810, 160
|
||||
cx_closed, cy_closed = 720, 250
|
||||
|
||||
# Draw shaft
|
||||
# Direction vector from closed to open: (90, -90)
|
||||
# Unit vector: dx = 0.7071, dy = -0.7071
|
||||
# Perpendicular vector: px = 0.7071, py = 0.7071
|
||||
dx, dy = 0.7071, -0.7071
|
||||
px, py = 0.7071, 0.7071
|
||||
half_w = 12
|
||||
|
||||
p1 = (cx_closed - half_w * px, cy_closed - half_w * py)
|
||||
p2 = (cx_closed + half_w * px, cy_closed + half_w * py)
|
||||
p3 = (cx_open + half_w * px, cy_open + half_w * py)
|
||||
p4 = (cx_open - half_w * px, cy_open - half_w * py)
|
||||
|
||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
||||
|
||||
# Draw open end outer circle
|
||||
draw.ellipse([cx_open - 38, cy_open - 38, cx_open + 38, cy_open + 38], fill=color)
|
||||
|
||||
# Draw closed end outer circle
|
||||
draw.ellipse([cx_closed - 30, cy_closed - 30, cx_closed + 30, cy_closed + 30], fill=color)
|
||||
|
||||
# Cut out the slot in open end (white polygon)
|
||||
# Slot of width 22, length 50, starting at cx_open, cy_open and going in direction (dx, dy)
|
||||
slot_half_w = 11
|
||||
s1 = (cx_open - slot_half_w * px, cy_open - slot_half_w * py)
|
||||
s2 = (cx_open + slot_half_w * px, cy_open + slot_half_w * py)
|
||||
s3 = (cx_open + 50 * dx + slot_half_w * px, cy_open + 50 * dy + slot_half_w * py)
|
||||
s4 = (cx_open + 50 * dx - slot_half_w * px, cy_open + 50 * dy - slot_half_w * py)
|
||||
draw.polygon([s1, s2, s3, s4], fill=(255, 255, 255, 255))
|
||||
|
||||
# Draw a small circle at the base of the slot to make it round and clean
|
||||
draw.ellipse([cx_open - 11, cy_open - 11, cx_open + 11, cy_open + 11], fill=(255, 255, 255, 255))
|
||||
|
||||
# Cut out closed end inner hole (white circle)
|
||||
draw.ellipse([cx_closed - 15, cy_closed - 15, cx_closed + 15, cy_closed + 15], fill=(255, 255, 255, 255))
|
||||
|
||||
im = Image.open('scratch/logo_s_only.png')
|
||||
draw = ImageDraw.Draw(im)
|
||||
color = (29, 32, 47, 255)
|
||||
|
||||
draw_wrench(draw, color)
|
||||
im.save('scratch/service_logo_test.png')
|
||||
print("Saved service logo to scratch/service_logo_test.png")
|
||||
@@ -1,60 +0,0 @@
|
||||
import re
|
||||
import json
|
||||
|
||||
def extract_language_maps(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Let's find language blocks like: "en": { ... }, "fr": { ... }
|
||||
# To parse these blocks, we find the starting position of each language identifier
|
||||
# and scan until the matching closing brace.
|
||||
languages = ['tr', 'fr', 'de', 'es', 'fa', 'el', 'ur', 'hi', 'ru', 'it', 'zh']
|
||||
extracted = {}
|
||||
|
||||
for lang in languages:
|
||||
# Regex to find: "lang": { or 'lang': {
|
||||
pattern = re.compile(r'[\'"]' + lang + r'[\'"]\s*:\s*\{')
|
||||
match = pattern.search(content)
|
||||
if not match:
|
||||
print(f"Language {lang} map start not found!")
|
||||
continue
|
||||
|
||||
start_idx = match.end()
|
||||
# Find matching closing brace
|
||||
brace_count = 1
|
||||
current_idx = start_idx
|
||||
while brace_count > 0 and current_idx < len(content):
|
||||
char = content[current_idx]
|
||||
if char == '{':
|
||||
brace_count += 1
|
||||
elif char == '}':
|
||||
brace_count -= 1
|
||||
current_idx += 1
|
||||
|
||||
block = content[start_idx:current_idx-1]
|
||||
|
||||
# Now parse the key-value pairs inside the block
|
||||
# Example: "key": "value", or 'key': 'value',
|
||||
# Handle escaped quotes.
|
||||
# Pattern: (['"])(.*?)\1\s*:\s*(['"])(.*?)\3\s*(?:,|$)
|
||||
kv_pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
||||
kv_matches = kv_pattern.findall(block)
|
||||
|
||||
lang_map = {}
|
||||
for kv in kv_matches:
|
||||
key = kv[1]
|
||||
val = kv[3]
|
||||
# Unescape quotes
|
||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
||||
lang_map[key] = val
|
||||
|
||||
extracted[lang] = lang_map
|
||||
print(f"Extracted {lang}: {len(lang_map)} keys")
|
||||
|
||||
return extracted
|
||||
|
||||
extracted = extract_language_maps("scratch/legacy_translations.dart")
|
||||
with open("scratch/legacy_extracted_languages.json", "w", encoding="utf-8") as f:
|
||||
json.dump(extracted, f, ensure_ascii=False, indent=2)
|
||||
print("Saved legacy extracted languages to JSON.")
|
||||
@@ -1,629 +0,0 @@
|
||||
#!/usr/bin/env python3.13
|
||||
"""
|
||||
Generate the updated feasibility study (Version 3.1) for Siro App.
|
||||
Produces both .docx and .html output.
|
||||
"""
|
||||
|
||||
from docx import Document
|
||||
from docx.shared import Inches, Pt, Cm, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.section import WD_ORIENT
|
||||
from docx.oxml.ns import qn
|
||||
from docx.oxml import OxmlElement
|
||||
import os
|
||||
|
||||
OUTPUT_DIR = "/Users/hamzaaleghwairyeen/development/App/Siro"
|
||||
|
||||
# ─── Colours ───
|
||||
PRIMARY = RGBColor(0x1E, 0x3A, 0x8A)
|
||||
DARK = RGBColor(0x0F, 0x17, 0x2A)
|
||||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||||
GREY = RGBColor(0x47, 0x55, 0x69)
|
||||
LGREY = RGBColor(0xF1, 0xF5, 0xF9)
|
||||
GREEN = RGBColor(0x16, 0x7A, 0x34)
|
||||
RED = RGBColor(0xB9, 0x1C, 0x1C)
|
||||
|
||||
def set_cell_shading(cell, color_hex):
|
||||
"""Set cell background colour."""
|
||||
shading = OxmlElement('w:shd')
|
||||
shading.set(qn('w:val'), 'clear')
|
||||
shading.set(qn('w:color'), 'auto')
|
||||
shading.set(qn('w:fill'), color_hex)
|
||||
cell._tc.get_or_add_tcPr().append(shading)
|
||||
|
||||
def add_table(doc, headers, rows, col_widths=None, header_color="1e3a8a", header_text_color="FFFFFF"):
|
||||
"""Add a formatted table."""
|
||||
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
|
||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
table.style = 'Table Grid'
|
||||
|
||||
# Header row
|
||||
for i, h in enumerate(headers):
|
||||
cell = table.rows[0].cells[i]
|
||||
cell.text = ''
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
run = p.add_run(h)
|
||||
run.bold = True
|
||||
run.font.size = Pt(10)
|
||||
run.font.color.rgb = WHITE
|
||||
set_cell_shading(cell, header_color)
|
||||
|
||||
# Data rows
|
||||
for r_idx, row in enumerate(rows):
|
||||
for c_idx, val in enumerate(row):
|
||||
cell = table.rows[r_idx + 1].cells[c_idx]
|
||||
cell.text = ''
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if c_idx > 0 else WD_ALIGN_PARAGRAPH.RIGHT
|
||||
run = p.add_run(str(val))
|
||||
run.font.size = Pt(9.5)
|
||||
if r_idx == len(rows) - 1:
|
||||
run.bold = True
|
||||
if c_idx == 0:
|
||||
run.bold = True
|
||||
|
||||
if col_widths:
|
||||
for i, w in enumerate(col_widths):
|
||||
for row in table.rows:
|
||||
row.cells[i].width = Cm(w)
|
||||
return table
|
||||
|
||||
def add_heading(doc, text, level=1):
|
||||
h = doc.add_heading(text, level=level)
|
||||
h.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
for run in h.runs:
|
||||
run.font.color.rgb = PRIMARY if level <= 2 else DARK
|
||||
return h
|
||||
|
||||
def add_para(doc, text, bold=False, size=10, color=None, align=WD_ALIGN_PARAGRAPH.RIGHT):
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = align
|
||||
run = p.add_run(text)
|
||||
run.bold = bold
|
||||
run.font.size = Pt(size)
|
||||
if color:
|
||||
run.font.color.rgb = color
|
||||
return p
|
||||
|
||||
def add_bullet(doc, text, bold_prefix=""):
|
||||
p = doc.add_paragraph(style='List Bullet')
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
if bold_prefix:
|
||||
run = p.add_run(bold_prefix)
|
||||
run.bold = True
|
||||
run.font.size = Pt(10)
|
||||
run = p.add_run(text)
|
||||
run.font.size = Pt(10)
|
||||
return p
|
||||
|
||||
|
||||
def generate_docx():
|
||||
doc = Document()
|
||||
|
||||
# Set RTL for entire document
|
||||
style = doc.styles['Normal']
|
||||
style.font.name = 'Calibri'
|
||||
style.font.size = Pt(10)
|
||||
style.element.rPr.rFonts.set(qn('w:eastAsia'), 'Calibri')
|
||||
|
||||
# ── Title Page ──
|
||||
for _ in range(4):
|
||||
doc.add_paragraph()
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
run = p.add_run("دراسة الجدوى الاقتصادية\nتطبيق سيرو للنقل الذكي")
|
||||
run.bold = True
|
||||
run.font.size = Pt(28)
|
||||
run.font.color.rgb = PRIMARY
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
run = p.add_run("الإصدار الثالث — يونيو 2026\nنموذج الدفع الشهري المرحلي")
|
||||
run.font.size = Pt(16)
|
||||
run.font.color.rgb = GREY
|
||||
|
||||
doc.add_paragraph()
|
||||
|
||||
# Key metrics boxes
|
||||
metrics = [
|
||||
("السوق المستهدف", "سوريا — دمشق الكبرى"),
|
||||
("نموذج الاستثمار", "دفع شهري مرحلي ($8,000/شهر)"),
|
||||
("رأس المال التأسيسي", "$11,500 - $12,000 (دفعة واحدة)"),
|
||||
("نقطة الخروج", "نهاية الشهر الخامس إذا لم تتحقق المؤشرات"),
|
||||
("نطاق نقطة التعادل", "الشهر السابع حتى التاسع"),
|
||||
("أقصى تعرض للمستثمر", "$51,500 - $52,000 (عند الخروج المبكر)"),
|
||||
("إجمالي الاستثمار المتوقع", "$67,500 — $84,000"),
|
||||
]
|
||||
for label, val in metrics:
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
run = p.add_run(f"{label}: ")
|
||||
run.bold = True
|
||||
run.font.size = Pt(11)
|
||||
run = p.add_run(val)
|
||||
run.font.size = Pt(11)
|
||||
run.font.color.rgb = PRIMARY
|
||||
|
||||
doc.add_page_break()
|
||||
|
||||
# ── 1. Executive Summary ──
|
||||
add_heading(doc, "أولاً: الملخص التنفيذي", 1)
|
||||
|
||||
add_para(doc,
|
||||
"سيرو تطبيق نقل ذكي جاهز للإطلاق في السوق السوري، يعمل على بنية تحتية مستقلة تقنياً لا تعتمد على "
|
||||
"أي خدمات خارجية مكلفة أو مقيدة جغرافياً. يتميز بأدنى عمولة في السوق (11%) مقابل 17-20% لدى المنافسين، "
|
||||
"مما يجعله الخيار المنطقي للسائق والراكب معاً.")
|
||||
|
||||
add_para(doc,
|
||||
"تتبنى هذه الخطة نموذج دفع شهري مرحلي يحمي المستثمر من تجميد رأس المال، ويضمن استمرارية تشغيلية مرتبطة "
|
||||
"بالأداء الفعلي. وجود شرط خروج صريح في الشهر الخامس يُعطي الطرفين وضوحاً تاماً في المخاطر والتوقعات.")
|
||||
|
||||
add_para(doc, "أبرز نقاط القوة التنافسية", bold=True, size=11)
|
||||
bullets = [
|
||||
("▸ المنتج مكتمل تقنياً ومختبر ميدانياً — لا مخاطر تطوير"),
|
||||
("▸ تجربة الطيار: 1,447 سائق انضموا بـ$1,400 إعلانات فقط — بدون حوافز"),
|
||||
("▸ أدنى عمولة في السوق: 11% مقابل 17-20% عند المنافسين"),
|
||||
("▸ بنية تحتية ذاتية توفر 60% من تكاليف التشغيل مقارنة بالنظراء"),
|
||||
("▸ المؤسس يغطي وظائف 5 أشخاص — وفر $3,000-5,000/شهر في الرواتب"),
|
||||
("▸ نموذج استثمار شهري مرحلي: صفر مخاطر تجميد رأس المال"),
|
||||
]
|
||||
for b in bullets:
|
||||
add_bullet(doc, b)
|
||||
|
||||
# Key indicators table
|
||||
add_para(doc, "مؤشرات الأداء الرئيسية", bold=True, size=11)
|
||||
add_table(doc,
|
||||
["المؤشر", "القيمة"],
|
||||
[
|
||||
["رأس المال التأسيسي (مرة واحدة)", "$11,500 - $12,000"],
|
||||
["المصاريف التشغيلية الشهرية", "$8,000/شهر"],
|
||||
["نقطة الخروج للمستثمر", "نهاية الشهر الخامس إذا لم تتحقق المؤشرات"],
|
||||
["نطاق التعادل", "الشهر 7 — 9"],
|
||||
["أقصى خسارة عند الخروج المبكر", "$51,500 - $52,000"],
|
||||
["إجمالي الاستثمار حتى التعادل", "$67,500 — $84,000"],
|
||||
["عمولة التطبيق", "11% من كل رحلة"],
|
||||
["متوسط حصة الشركة للرحلة", "$0.30 / رحلة"],
|
||||
["هدف الرحلات عند التعادل", "889 رحلة/يوم"],
|
||||
],
|
||||
col_widths=[12, 6]
|
||||
)
|
||||
|
||||
# ── 2. Vision & Business Model ──
|
||||
add_heading(doc, "ثانياً: الرؤية والنموذج التجاري", 1)
|
||||
|
||||
add_para(doc, "الرؤية", bold=True, size=11)
|
||||
add_para(doc, "أن يكون سيرو المنصة الأولى للنقل الذكي الموثوق في سوريا، بعمولة عادلة للسائق وخدمة موثوقة للراكب، على بنية رقمية مستقلة وسيادية.")
|
||||
|
||||
add_para(doc, "نموذج الإيرادات", bold=True, size=11)
|
||||
add_bullet(doc, "عمولة 11% من قيمة كل رحلة تُقتطع تلقائياً من أجرة السائق")
|
||||
add_bullet(doc, "متوسط قيمة الرحلة: $2.75 — حصة الشركة: $0.30/رحلة")
|
||||
add_bullet(doc, "مستقبلاً: إعلانات داخل التطبيق، اشتراكات السائق المميز، خدمات B2B للشركات والفنادق")
|
||||
|
||||
add_para(doc, "الميزة التنافسية — لماذا سيرو؟", bold=True, size=11)
|
||||
add_table(doc,
|
||||
["المعيار", "سيرو", "المنافسون"],
|
||||
[
|
||||
["عمولة التطبيق", "11%", "17% - 20%"],
|
||||
["خريطة المنصة", "IntaleqMaps — ذاتية $0", "Google Maps $5,000-15,000/شهر"],
|
||||
["نظام OTP", "Flash Call ذاتي — $0", "SMS مدفوع $0.03-0.05/رسالة"],
|
||||
["تكلفة التطوير المستمر", "المؤسس = المطور", "فريق خارجي مكلف"],
|
||||
["البنية التحتية", "خوادم ذاتية مستقلة", "Cloud APIs قابلة للتوقف"],
|
||||
["خدمة العملاء", "مدمجة + AI (Nabeeh)", "مراكز خارجية مكلفة"],
|
||||
],
|
||||
col_widths=[5, 6, 7]
|
||||
)
|
||||
|
||||
# ── 3. Market Analysis ──
|
||||
add_heading(doc, "ثالثاً: تحليل السوق السوري", 1)
|
||||
|
||||
add_heading(doc, "3-1: لماذا الآن؟", 2)
|
||||
add_para(doc, "تمر سوريا في مرحلة إعادة الإعمار وانفتاح اقتصادي تدريجي منذ نهاية 2024. دمشق الكبرى تشهد حركة تجارية متصاعدة مع نقص واضح في وسائل النقل المنظم.")
|
||||
|
||||
add_heading(doc, "3-2: حجم السوق", 2)
|
||||
add_table(doc,
|
||||
["المؤشر", "التقدير"],
|
||||
[
|
||||
["سكان دمشق الكبرى", "4 - 5 مليون نسمة"],
|
||||
["مستخدمو الهاتف الذكي", "65% - 70%"],
|
||||
["الرحلات اليومية المحتملة", "200,000 - 400,000 رحلة/يوم"],
|
||||
["حصة السوق المستهدفة سنة 1", "1% - 2% (1,500 - 2,000 رحلة/يوم)"],
|
||||
["نتيجة تجربة الطيار", "1,447 سائق سجلوا بـ$1,400 تسويق فقط"],
|
||||
],
|
||||
col_widths=[10, 8]
|
||||
)
|
||||
|
||||
add_heading(doc, "3-3: المنافسون الرئيسيون", 2)
|
||||
add_table(doc,
|
||||
["التطبيق", "نقطة الضعف", "فرصتنا"],
|
||||
[
|
||||
["YallaGo", "عمولة 20% تُرهق السائق", "11% = ولاء السائق يتحول إلينا"],
|
||||
["Zakinn", "عمولة 17% تُرهق السائق", "نبدأ من المنطقة الأكثر طلباً"],
|
||||
["Tafaddal", "تجربة مستخدم ضعيفة", "UX متقدم + تطبيق سائق أفضل"],
|
||||
["سيرو — ميزتنا", "أدنى عمولة + بنية مستقلة", "الأصعب تقليداً في السوق"],
|
||||
],
|
||||
col_widths=[4, 6, 8]
|
||||
)
|
||||
|
||||
# ── 4. Operational Plan ──
|
||||
doc.add_page_break()
|
||||
add_heading(doc, "رابعاً: الخطة التشغيلية", 1)
|
||||
|
||||
add_heading(doc, "4-1: اختيار موقع المكتب في دمشق", 2)
|
||||
add_para(doc, "يشترط الترخيص القانوني وجود مقر موثق. الميزانية المخصصة: $600/شهر.")
|
||||
|
||||
add_table(doc,
|
||||
["المنطقة", "التقييم", "الإيجار الشهري", "الملاحظة"],
|
||||
[
|
||||
["المزة", "★★★★★", "$500 - $700", "الأفضل: مقر الشركات الكبرى"],
|
||||
["كفرسوسة", "★★★★☆", "$400 - $600", "قريب من المزة، أسعار معقولة"],
|
||||
["الصالحية", "★★★☆☆", "$400 - $550", "مركزي، وصول جيد"],
|
||||
["أبو رمانة", "★★☆☆☆", "$700 - $1,000", "مرموق لكن يتجاوز الميزانية"],
|
||||
["برزة", "★★☆☆☆", "$280 - $400", "أرخص لكن أقل احترافية"],
|
||||
],
|
||||
col_widths=[3, 2.5, 3.5, 8]
|
||||
)
|
||||
add_para(doc, "التوصية: كفرسوسة أو المزة — 50-70 م² مع هامش تفاوض", bold=True)
|
||||
|
||||
# 4-2: Office Furniture (revised)
|
||||
add_heading(doc, "4-2: المشتريات والتجهيزات المكتبية", 2)
|
||||
|
||||
add_para(doc, "أ — الأثاث المكتبي", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["البند", "الكمية", "سعر الوحدة", "الإجمالي"],
|
||||
[
|
||||
["مكتب رئيسي مع أدراج", 1, "$150", "$150"],
|
||||
["مكاتب موظفين بسيطة", 3, "$90", "$270"],
|
||||
["كرسي مكتبي دوّار رئيسي", 1, "$110", "$110"],
|
||||
["كراسي موظفين", 3, "$60", "$180"],
|
||||
["طاولة اجتماعات صغيرة (4 أشخاص)", 1, "$80", "$80"],
|
||||
["كراسي اجتماعات", 4, "$50", "$200"],
|
||||
["برادي (ستائر) للمكتب", "—", "—", "$200"],
|
||||
["رفوف تخزين بسيطة", "—", "—", "$50"],
|
||||
["مراوح (عدد 2)", 2, "$40", "$80"],
|
||||
["إجمالي الأثاث", "", "", "$1,320"],
|
||||
],
|
||||
col_widths=[7, 2, 2.5, 2.5]
|
||||
)
|
||||
|
||||
add_para(doc, "ب — المعدات والتجهيزات", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["البند", "الإجمالي"],
|
||||
[
|
||||
["راوتر WiFi احترافي", "$80"],
|
||||
["طابعة/ماسح ضوئي", "$65"],
|
||||
["إكسسوارات متنوعة", "$100"],
|
||||
["مكيف هواء (شراء + تركيب)", "$450"],
|
||||
["قرطاسية ومستلزمات (3 أشهر)", "$80"],
|
||||
["أدوات ضيافة", "$80"],
|
||||
["إجمالي التجهيزات", "$855"],
|
||||
],
|
||||
col_widths=[10, 4]
|
||||
)
|
||||
|
||||
add_para(doc, "ج — أثاث وتجهيزات سكن المؤسس", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["البند", "الإجمالي"],
|
||||
[
|
||||
["سرير (عدد 2)", "$80"],
|
||||
["فرشة (عدد 2)", "$180"],
|
||||
["إحرامات + مخدات (عدد 2)", "$40"],
|
||||
["برادي للسكن", "$100"],
|
||||
["ثلاجة صغيرة", "$100"],
|
||||
["غاز صغير + أدوات مطبخ", "$100"],
|
||||
["سخان مياه", "$70"],
|
||||
["سفري + أدوات ضيافة", "$25"],
|
||||
["إجمالي تجهيزات السكن", "$695"],
|
||||
],
|
||||
col_widths=[10, 4]
|
||||
)
|
||||
|
||||
# 4-3: Technical Equipment
|
||||
add_heading(doc, "4-3: أجهزة التطوير والمعدات التقنية", 2)
|
||||
add_table(doc,
|
||||
["البند", "الإجمالي", "ملاحظة"],
|
||||
[
|
||||
["MacBook Pro M4 Pro Max (40 GPU)", "$3,100", "جهاز التطوير والإدارة الرئيسي"],
|
||||
["iPhone (أحدث إصدار)", "$500", "اختبار تطبيق iOS"],
|
||||
["جهاز Android (أحدث إصدار)", "$300", "اختبار تطبيق Android"],
|
||||
["باقي أجهزة التطوير", "$1,100", "ملحقات وإكسسوارات تطوير"],
|
||||
["إجمالي أجهزة التطوير", "$5,000", ""],
|
||||
],
|
||||
col_widths=[8, 3.5, 5.5]
|
||||
)
|
||||
|
||||
add_para(doc, "ملاحظة: تم استبدال أجهزة الحاسوب المكتبي لخدمة العملاء بهواتف ذكية (3 × $150 ضمن بند هواتف خدمة العملاء في CAPEX).", size=9, color=GREY)
|
||||
|
||||
# ── 5. HR Plan ──
|
||||
add_heading(doc, "خامساً: خطة الموارد البشرية", 1)
|
||||
|
||||
add_heading(doc, "5-1: الهيكل الوظيفي والرواتب", 2)
|
||||
add_table(doc,
|
||||
["المسمى الوظيفي", "العدد", "الراتب الشهري", "الإجمالي"],
|
||||
[
|
||||
["المشغل الرئيسي / المؤسس التنفيذي", 1, "$3,500", "$3,500"],
|
||||
["ممثل خدمة العملاء", 3, "$110 - $130", "$400"],
|
||||
["إجمالي الرواتب الشهرية", "4 أشخاص", "", "$3,900"],
|
||||
],
|
||||
col_widths=[7, 2, 3.5, 3]
|
||||
)
|
||||
|
||||
add_para(doc, "ملاحظات:", bold=True, size=9)
|
||||
add_bullet(doc, "تم إلغاء بند مدير السوشيال ميديا (فريلانسر) — المؤسس يدير التسويق الرقمي مباشرة")
|
||||
add_bullet(doc, "تم إلغاء بند السكرتيرة الإدارية — المهام توزع على فريق خدمة العملاء والمؤسس")
|
||||
add_bullet(doc, "فريق خدمة العملاء: 3 موظفين براتب $110-130/شهر ($400 إجمالي)")
|
||||
add_bullet(doc, "برنامج تدريب خدمة العملاء: أسبوعين (بدلاً من 4 أسابيع)")
|
||||
|
||||
# ── 6. Financial Plan ──
|
||||
doc.add_page_break()
|
||||
add_heading(doc, "سادساً: الخطة المالية التفصيلية", 1)
|
||||
|
||||
add_heading(doc, "6-1: رأس المال التأسيسي — $9,000 (دفعة واحدة)", 2)
|
||||
add_para(doc, "يُصرف كاملاً عند بدء التأسيس قبل الإطلاق.")
|
||||
|
||||
add_table(doc,
|
||||
["البند", "المبلغ", "البيان"],
|
||||
[
|
||||
["شهادة اعتمادية (الهيئة الناظمة)", "$600", "ترخيص التطبيق"],
|
||||
["أتعاب المحامي والتخليص القانوني", "$1,500", "تأسيس الشركة + تراخيص"],
|
||||
["رسوم وزارة (سجل تجاري + وزارة نقل)", "$200", "رسوم حكومية"],
|
||||
["هواتف خدمة العملاء (3 أجهزة)", "$450", "3 × $150"],
|
||||
["أجهزة التطوير (Mac + iPhone + Android)", "$5,000", "حسب التفصيل في 4-3"],
|
||||
["لابتوب للسيرفرات وإدارة الإعلانات", "$350", "جهاز منفصل لإدارة السيرفرات"],
|
||||
["تجهيز المكتب — أثاث", "$1,320", "حسب التفصيل في 4-2-أ"],
|
||||
["تجهيز المكتب — معدات وتجهيزات", "$855", "حسب التفصيل في 4-2-ب"],
|
||||
["تجهيزات سكن المؤسس", "$695", "أثاث وتجهيزات أساسية"],
|
||||
["تكاليف السفر والنقل والإقامة التأسيسية (أسبوعين)", "$400 - $800", "مواصلات + سكن مؤقت + تجهيز"],
|
||||
["إجمالي رأس المال التأسيسي", "$11,370 - $11,770 ≈ $11,500 - $12,000", ""],
|
||||
],
|
||||
col_widths=[8, 2.5, 6]
|
||||
)
|
||||
|
||||
add_heading(doc, "6-2: المصاريف التشغيلية الشهرية — $8,000/شهر", 2)
|
||||
add_table(doc,
|
||||
["البند", "المبلغ", "البيان"],
|
||||
[
|
||||
["راتب المشغل الرئيسي", "$3,500", "تقني + إداري + تسويق + عمليات"],
|
||||
["فريق خدمة العملاء × 3", "$400", "بمتوسط $133/موظف"],
|
||||
["سيرفرات وبنية سحابية", "$200", "استضافة + نسخ احتياطي"],
|
||||
["إيجار المكتب", "$600", "كفرسوسة / المزة"],
|
||||
["إيجار سكن المشغل", "$300", "ضمن خطة الرواتب"],
|
||||
["خدمات الإنترنت", "$45", "خط ثابت مزدوج"],
|
||||
["فاتورة الكهرباء", "$70", "مكتب + معدات"],
|
||||
["باقات خطوط هواتف (3 أرقام)", "$30", "لخدمة العملاء"],
|
||||
["إعلانات رقمية (Facebook + TikTok)", "$2,855", "المتبقي من الميزانية"],
|
||||
["إجمالي OPEX الشهري", "$8,000", ""],
|
||||
],
|
||||
col_widths=[7, 2.5, 6]
|
||||
)
|
||||
|
||||
add_para(doc, "ملاحظة: تم إلغاء بند إدارة السوشيال ميديا ($200) وبند السكرتيرة ($100). الفائض (~$355 إضافية) يُضاف إلى ميزانية الإعلانات الرقمية ($2,855 بدلاً من $2,500).", size=9, color=GREY)
|
||||
|
||||
# 6-3: Driver Incentives
|
||||
add_heading(doc, "6-3: خطة حوافز السائقين", 2)
|
||||
add_table(doc,
|
||||
["الفترة", "السائقون", "التكلفة", "المصدر"],
|
||||
[
|
||||
["الشهر الأول", "100 سائق × $15", "$1,500", "من CAPEX (احتياطي)"],
|
||||
["الشهر الثاني", "120 سائق × $15", "$1,800", "من CAPEX (احتياطي)"],
|
||||
["الشهر الثالث", "150+ سائق", "من الإيرادات", "ذاتي التمويل"],
|
||||
["الشهر الرابع+", "300-500 سائق", "من الإيرادات", "ذاتي التمويل"],
|
||||
],
|
||||
col_widths=[4, 4, 3.5, 4]
|
||||
)
|
||||
|
||||
# 6-4: Cash Flow Table
|
||||
add_heading(doc, "6-4: جدول التدفق النقدي الشهري", 2)
|
||||
add_table(doc,
|
||||
["الشهر", "صرف المستثمر", "الإيرادات", "العجز", "الإجمالي", "رحلات/يوم", "سائق نشط"],
|
||||
[
|
||||
["التأسيس", "$11,500-12,000", "—", "-$11,500-12,000", "$11,500-12,000", "—", "—"],
|
||||
["1", "$8,000", "$270", "-$7,730", "$19,500-20,000", "30", "100"],
|
||||
["2", "$8,000", "$630", "-$7,370", "$27,500-28,000", "70", "120"],
|
||||
["3", "$8,000", "$1,350", "-$6,650", "$35,500-36,000", "150", "220"],
|
||||
["★4 — فحص", "$8,000", "$3,150", "-$4,850", "$43,500-44,000", "350", "350"],
|
||||
["★5 — خروج", "$8,000", "$5,400", "-$2,600", "$51,500-52,000", "600", "480"],
|
||||
["6", "$8,000", "$6,750", "-$1,250", "$59,500-60,000", "750", "550"],
|
||||
["⚡7", "$8,000", "$8,100", "+$100", "$67,500-68,000", "900", "630"],
|
||||
["⚡8", "$8,000", "$9,450", "+$1,450", "$75,500-76,000", "1,050", "750"],
|
||||
["⚡9", "$8,000", "$10,500", "+$2,500", "$83,500-84,000", "1,167", "840"],
|
||||
["10", "$0", "$11,250", "+$3,250", "—", "1,250", "900"],
|
||||
["11", "$0", "$12,375", "+$4,375", "—", "1,375", "980"],
|
||||
["12", "$0", "$13,500", "+$5,500", "—", "1,500", "1,050"],
|
||||
["13", "$0", "$14,400", "+$6,400", "—", "1,600", "1,100"],
|
||||
["14", "$0", "$15,300", "+$7,300", "—", "1,700", "1,150"],
|
||||
],
|
||||
col_widths=[2.5, 2.5, 2, 2, 2.5, 2, 2]
|
||||
)
|
||||
|
||||
# ── 7. Investment Structure ──
|
||||
doc.add_page_break()
|
||||
add_heading(doc, "سابعاً: هيكل الاستثمار — نموذج الدفع الشهري", 1)
|
||||
|
||||
add_para(doc, "آلية الدفع:", bold=True, size=11)
|
||||
add_bullet(doc, "المستثمر يدفع $11,500-$12,000 مرة واحدة عند التوقيع (CAPEX)")
|
||||
add_bullet(doc, "يدفع $8,000 شهرياً لتغطية التشغيل الكامل")
|
||||
add_bullet(doc, "الدفع يتوقف تلقائياً عندما تتجاوز الإيرادات $8,000/شهر")
|
||||
add_bullet(doc, "لا يوجد التزام بإجمالي محدد مقدماً")
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "جدول الدفع المتوقع (سيناريو قاعدي — تعادل الشهر 8):", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["الشهر", "المبلغ", "نوع الدفع", "حالة المشروع"],
|
||||
[
|
||||
["صفر", "$11,500-$12,000", "مرة واحدة", "تجهيز + ترخيص + تعيين"],
|
||||
["1", "$8,000", "شهري", "إطلاق ناعم"],
|
||||
["2", "$8,000", "شهري", "نمو متصاعد"],
|
||||
["3", "$8,000", "شهري", "حوافز ذاتية"],
|
||||
["4 — فحص", "$8,000", "شهري", "مراجعة أداء"],
|
||||
["5 — خروج", "$8,000", "شهري", "استمرار أو خروج"],
|
||||
["6", "$8,000", "شهري", "قرب التعادل"],
|
||||
["7", "$8,000", "شهري", "تعادل متفائل"],
|
||||
["8 — تعادل", "$8,000 (آخر)", "شهري", "الإيرادات ≥ $8,000"],
|
||||
["9+", "$0", "ذاتي", "المشروع يمول نفسه"],
|
||||
],
|
||||
col_widths=[3, 3, 3, 7]
|
||||
)
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "إجمالي تعرض المستثمر في كل سيناريو:", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["السيناريو", "التوقف", "الإجمالي", "ملاحظة"],
|
||||
[
|
||||
["خروج مبكر (فشل)", "نهاية ش5", "$51,500-$52,000", "أقصى خسارة ~$52,000"],
|
||||
["تعادل متفائل", "منتصف ش7", "$59,500-$60,000", "أفضل سيناريو"],
|
||||
["تعادل قاعدي", "منتصف ش8", "$67,500-$68,000", "الأرجح"],
|
||||
["تعادل محافظ", "منتصف ش9", "$75,500-$76,000", "نمو أبطأ"],
|
||||
],
|
||||
col_widths=[5, 3, 3, 5]
|
||||
)
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "لماذا لا يوجد احتياطي مالي في هذا النموذج؟", bold=True, size=11)
|
||||
add_para(doc, "في النموذج القديم (مبلغ واحد)، المستثمر يدفع كل شيء مقدماً والاحتياطي يجلس خاملاً. في النموذج الجديد (شهري):", size=10)
|
||||
add_bullet(doc, "المستثمر يدفع فقط ما صُرف فعلاً")
|
||||
add_bullet(doc, "لا توجد أموال خاملة — كل دولار يُشغَّل")
|
||||
add_bullet(doc, "المستثمر يخاطر بـ$51,000 كحد أقصى (عند الخروج)")
|
||||
add_bullet(doc, "نقطة الخروج هي الحماية الحقيقية بدلاً من الاحتياطي")
|
||||
|
||||
# ── 8. Exit Clause ──
|
||||
add_heading(doc, "ثامناً: شرط الخروج — بند الحماية", 1)
|
||||
|
||||
add_para(doc, "يحق للمستثمر إيقاف الدفعات الشهرية والخروج في نهاية الشهر الخامس إذا لم تتحقق المؤشرات.", size=10)
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "مؤشرات الأداء — نقطة الفحص (الشهر 4):", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["المؤشر", "الحد الأدنى", "الحد المثالي"],
|
||||
[
|
||||
["الرحلات اليومية", "70/يوم", "150/يوم"],
|
||||
["السائقون المسجلون", "150", "350"],
|
||||
["السائقون النشطون", "50", "150"],
|
||||
["الإيرادات الشهرية", "$630", "$1,350"],
|
||||
["معدل احتجاز السائق", "50%", "70%"],
|
||||
["تقييم التطبيق", "3.0+", "3.5+"],
|
||||
],
|
||||
col_widths=[6, 4, 4]
|
||||
)
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "مؤشرات الأداء — نقطة القرار (الشهر 5):", bold=True, size=10)
|
||||
add_table(doc,
|
||||
["المؤشر", "حد الاستمرار", "الخروج إذا أقل من"],
|
||||
[
|
||||
["الرحلات اليومية", "120/يوم", "70/يوم"],
|
||||
["السائقون النشطون", "100", "50"],
|
||||
["الإيرادات الشهرية", "$1,080+", "$630"],
|
||||
["نمو أسبوعي", "+10% متواصل", "ثبات أو تراجع"],
|
||||
["عقود B2B", "عقد واحد", "صفر عقود"],
|
||||
],
|
||||
col_widths=[6, 4, 4]
|
||||
)
|
||||
|
||||
# ── 9. Break-Even ──
|
||||
add_heading(doc, "تاسعاً: تحليل نقطة التعادل — نطاق الشهر 7 إلى 9", 1)
|
||||
|
||||
add_para(doc, "لماذا نطاق وليس رقماً ثابتاً؟", bold=True, size=10)
|
||||
add_para(doc, "السوق السوري في مرحلة إعادة الإعمار = سوق متقلب بطبيعته. النطاق (7-9) أكثر صدقاً وأكثر حماية لكلا الطرفين.")
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_table(doc,
|
||||
["السيناريو", "نقطة التعادل", "إجمالي الاستثمار", "الوصف"],
|
||||
[
|
||||
["متفائل", "الشهر 7", "$59,500-$60,000", "نمو سريع، B2B مبكر"],
|
||||
["قاعدي (الأرجح)", "الشهر 8", "$67,500-$68,000", "نمو طبيعي"],
|
||||
["محافظ", "الشهر 9", "$75,500-$76,000", "سوق متقلب"],
|
||||
["خروج مبكر", "لا تعادل", "$51,500-$52,000", "آخر دفعة ش5"],
|
||||
],
|
||||
col_widths=[4, 3, 4, 5]
|
||||
)
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_para(doc, "معادلة التعادل:", bold=True, size=11)
|
||||
add_para(doc, "889 رحلة/يوم (عند $0.30/رحلة × 30 يوماً = $8,010)", bold=True, size=12, color=PRIMARY)
|
||||
add_table(doc,
|
||||
["المعطى", "الرقم", "كيف وصلنا إليه"],
|
||||
[
|
||||
["عمولة الشركة لكل رحلة", "$0.30", "11% من متوسط رحلة $2.75"],
|
||||
["أيام الشهر", "30", ""],
|
||||
["OPEX الشهري المستهدف", "$8,000", ""],
|
||||
["رحلات التعادل اليومية", "889/يوم", "$8,000 ÷ $0.30 ÷ 30"],
|
||||
["تعادل الشهر 7 (900/يوم)", "$8,100 > $8,000 ✓", "متحقق"],
|
||||
],
|
||||
col_widths=[6, 4, 6]
|
||||
)
|
||||
|
||||
# ── 10. Risk Analysis ──
|
||||
add_heading(doc, "عاشراً: تحليل المخاطر وخطط التخفيف", 1)
|
||||
add_table(doc,
|
||||
["المخاطرة", "التأثير", "الاحتمالية", "خطة التخفيف"],
|
||||
[
|
||||
["تأخر الترخيص", "عالٍ", "متوسطة", "البدء قبل الإطلاق بشهرين"],
|
||||
["بطء نمو السائقين", "عالٍ", "منخفضة", "1,447 سائق بـ$1,400 — مثبت"],
|
||||
["انخفاض الإيرادات", "عالٍ", "متوسطة", "شرط الخروج ش5 يحمي المستثمر"],
|
||||
["تقلبات أمنية/سياسية", "عالٍ", "منخفضة-متوسطة", "بنية مستقلة - لا اعتماد على APIs غربية"],
|
||||
["دخول منافس جديد", "متوسط", "متوسطة", "11% عمولة = عتبة تنافسية شبه مستحيلة"],
|
||||
["مشاكل تقنية", "متوسط", "منخفضة", "المطور = المؤسس — استجابة فورية"],
|
||||
["فشل الإعلانات", "متوسط", "منخفضة", "تجربة سابقة مثبتة + A/B Testing"],
|
||||
],
|
||||
col_widths=[5, 2, 3, 6]
|
||||
)
|
||||
|
||||
# ── 11. Roadmap ──
|
||||
add_heading(doc, "حادي عشر: خارطة الطريق والأهداف التشغيلية", 1)
|
||||
add_table(doc,
|
||||
["المرحلة", "الشهر", "الأهداف", "مؤشرات النجاح"],
|
||||
[
|
||||
["التأسيس", "قبل الإطلاق", "ترخيص + مكتب + توظيف", "وثائق قانونية + فريق جاهز"],
|
||||
["الإطلاق الناعم", "ش 1-2", "100-120 سائق محفز", "70+ رحلة/يوم"],
|
||||
["بناء الزخم", "ش 3", "حوافز ذاتية + B2B", "150 رحلة/يوم"],
|
||||
["نقطة الفحص", "ش 4", "مراجعة مع المستثمر", "70-150 رحلة/يوم | $630-$1,350"],
|
||||
["قرار الاستمرار", "ش 5", "استمرار أو خروج", "120 رحلة/يوم | عقد B2B"],
|
||||
["الاقتراب من التعادل", "ش 6", "750 رحلة/يوم", "عجز $1,250 فقط"],
|
||||
["⚡ نطاق التعادل", "ش 7-9", "الإيرادات ≥ OPEX", "889+ رحلة/يوم"],
|
||||
["النمو الذاتي", "ش 10-12", "فائض شهري", "$3,250-$5,500 فائض"],
|
||||
["التوسع الجغرافي", "ش 13+", "حلب أو اللاذقية", "1,600+ رحلة/يوم"],
|
||||
],
|
||||
col_widths=[4, 2, 4, 6]
|
||||
)
|
||||
|
||||
# ── 12. Conclusion ──
|
||||
doc.add_page_break()
|
||||
add_heading(doc, "ثاني عشر: الخلاصة والتوصية النهائية", 1)
|
||||
|
||||
add_para(doc, "ملخص نقاط الثقة للمستثمر:", bold=True, size=11)
|
||||
add_bullet(doc, "المنتج جاهز ومختبر — 1,447 سائق التحقوا بـ$1,400 فقط")
|
||||
add_bullet(doc, "أدنى عمولة في السوق (11%) = ميزة تنافسية دائمة")
|
||||
add_bullet(doc, "نموذج الدفع الشهري يحمي المستثمر — لا تجميد لرأس المال")
|
||||
add_bullet(doc, "شرط الخروج الواضح يضع سقفاً لأقصى خسارة (~$52,000)")
|
||||
add_bullet(doc, "نقطة التعادل (7-9 أشهر) واقعية ومبنية على بيانات حقيقية")
|
||||
add_bullet(doc, "البنية التقنية المستقلة = مقاومة للعقوبات والقيود")
|
||||
|
||||
add_para(doc, "", size=6)
|
||||
add_table(doc,
|
||||
["المؤشر", "القيمة"],
|
||||
[
|
||||
["رأس المال التأسيسي (مرة واحدة)", "$11,500-$12,000 — عند توقيع الاتفاقية"],
|
||||
["المصاريف التشغيلية الشهرية", "$8,000/شهر — يتوقف عند التعادل"],
|
||||
["أقصى تعرض للمستثمر", "$51,500-$52,000 — نقطة الخروج: نهاية الشهر الخامس"],
|
||||
["إجمالي الاستثمار حتى التعادل", "$67,500 — $84,000"],
|
||||
["نطاق التعادل", "الشهر السابع إلى التاسع"],
|
||||
],
|
||||
col_widths=[10, 8]
|
||||
)
|
||||
|
||||
add_para(doc, "", size=8)
|
||||
add_para(doc, "هذه الدراسة أُعدت بناءً على بيانات حقيقية من السوق السوري وتجربة ميدانية فعلية. كل رقم فيها مبني على افتراضات محافظة.", bold=True, size=10, align=WD_ALIGN_PARAGRAPH.CENTER)
|
||||
add_para(doc, "— نهاية دراسة الجدوى — الإصدار الثالث — يونيو 2026 —", size=9, color=GREY, align=WD_ALIGN_PARAGRAPH.CENTER)
|
||||
|
||||
# Save
|
||||
docx_path = os.path.join(OUTPUT_DIR, "دراسة_الجدوى_سيرو_الإصدار_الثالث.docx")
|
||||
doc.save(docx_path)
|
||||
print(f"✅ DOCX saved: {docx_path}")
|
||||
return docx_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_docx()
|
||||
@@ -1,42 +0,0 @@
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
|
||||
def parse_dart_map(filepath):
|
||||
translations = {}
|
||||
if not os.path.exists(filepath):
|
||||
return translations
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
||||
matches = pattern.findall(content)
|
||||
for match in matches:
|
||||
key = match[1]
|
||||
val = match[3]
|
||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
||||
translations[key] = val
|
||||
|
||||
return translations
|
||||
|
||||
ar_eg = parse_dart_map("siro_driver/lib/controller/local/ar_eg.dart")
|
||||
ar_jo = parse_dart_map("siro_driver/lib/controller/local/ar_jo.dart")
|
||||
ar_sy = parse_dart_map("siro_driver/lib/controller/local/ar_sy.dart")
|
||||
|
||||
with open("siro_driver_translations_data.json", "r", encoding="utf-8") as f:
|
||||
json_data = json.load(f)
|
||||
|
||||
missing_keys_list = json_data.get('missing_keys', [])
|
||||
|
||||
ar_sy_keys = set(ar_sy.keys())
|
||||
ar_jo_keys = set(ar_jo.keys())
|
||||
ar_eg_keys = set(ar_eg.keys())
|
||||
missing_keys_set = set(missing_keys_list)
|
||||
|
||||
all_keys = ar_sy_keys.union(ar_jo_keys).union(ar_eg_keys).union(missing_keys_set)
|
||||
print(f"Total unique keys in union: {len(all_keys)}")
|
||||
|
||||
print(f"Keys in ar_eg not in ar_sy: {ar_eg_keys - ar_sy_keys}")
|
||||
print(f"Keys in ar_sy not in ar_jo: {ar_sy_keys - ar_jo_keys}")
|
||||
print(f"Keys in ar_jo not in ar_sy: {ar_jo_keys - ar_sy_keys}")
|
||||
|
Before Width: | Height: | Size: 282 KiB |
@@ -1,289 +0,0 @@
|
||||
import hmac
|
||||
import hashlib
|
||||
import json
|
||||
import base64
|
||||
import uuid
|
||||
|
||||
# Simulation configuration / Secrets
|
||||
SECRET_KEY = "siro_super_secret_jwt_key"
|
||||
SECRET_KEY_HMAC = "siro_super_secret_hmac_key"
|
||||
FP_PEPPER = "siro_fp_pepper_salt"
|
||||
S2S_SHARED_KEY = "s2s_shared_key_12345"
|
||||
|
||||
# Mock Database
|
||||
db = {
|
||||
"payments": [],
|
||||
"passengerWallet": {},
|
||||
"driverWallet": {},
|
||||
"invoices": {},
|
||||
"security_logs": []
|
||||
}
|
||||
|
||||
def init_user(user_id, balance, is_driver=False):
|
||||
if is_driver:
|
||||
db["driverWallet"][user_id] = balance
|
||||
else:
|
||||
db["passengerWallet"][user_id] = balance
|
||||
|
||||
# Helper functions for Client
|
||||
def generate_jwt(user_id, device_fp):
|
||||
# Mock JWT creation payload
|
||||
header = base64.b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).decode().replace("=", "")
|
||||
hashed_fp = hashlib.sha256((device_fp + FP_PEPPER).encode()).hexdigest()
|
||||
payload = base64.b64encode(json.dumps({
|
||||
"iss": "Tripz-Wallet",
|
||||
"user_id": user_id,
|
||||
"fingerPrint": hashed_fp,
|
||||
"exp": 1900000000 # future exp
|
||||
}).encode()).decode().replace("=", "")
|
||||
signature = hmac.new(SECRET_KEY.encode(), f"{header}.{payload}".encode(), hashlib.sha256).digest()
|
||||
encoded_signature = base64.b64encode(signature).decode().replace("=", "").replace("+", "-").replace("/", "_")
|
||||
return f"{header}.{payload}.{encoded_signature}"
|
||||
|
||||
def calculate_hmac_header(user_id):
|
||||
return hmac.new(SECRET_KEY_HMAC.encode(), user_id.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
# Helper functions for Server Authentication
|
||||
def authenticate_request(headers, payload_user_id):
|
||||
# 1. JWT verification
|
||||
auth_header = headers.get("Authorization", "")
|
||||
if not auth_header.startswith("Bearer "):
|
||||
return False, "Authorization token required"
|
||||
token = auth_header.split(" ")[1]
|
||||
|
||||
try:
|
||||
parts = token.split(".")
|
||||
if len(parts) != 3:
|
||||
return False, "Invalid token structure"
|
||||
# Parse payload
|
||||
payload_b64 = parts[1]
|
||||
payload_b64 += "=" * ((4 - len(payload_b64) % 4) % 4)
|
||||
payload = json.loads(base64.b64decode(payload_b64).decode())
|
||||
except Exception as e:
|
||||
return False, f"JWT decode failed: {str(e)}"
|
||||
|
||||
# Check Issuer
|
||||
if payload.get("iss") != "Tripz-Wallet":
|
||||
return False, "Invalid token issuer"
|
||||
|
||||
# Check User ID match
|
||||
token_user_id = payload.get("user_id")
|
||||
if token_user_id != payload_user_id:
|
||||
return False, "User ID mismatch with token"
|
||||
|
||||
# 2. Device Fingerprint verification
|
||||
device_fp_header = headers.get("X-Device-FP")
|
||||
fp_in_token = payload.get("fingerPrint")
|
||||
if device_fp_header and fp_in_token:
|
||||
expected_fp = hashlib.sha256((device_fp_header + FP_PEPPER).encode()).hexdigest()
|
||||
if not hmac.compare_digest(expected_fp, fp_in_token):
|
||||
db["security_logs"].append(f"[WARNING] Device mismatch for user {payload_user_id}!")
|
||||
return False, "Device mismatch (possible Session Hijacking)"
|
||||
|
||||
# 3. HMAC Auth verification
|
||||
hmac_header = headers.get("X-HMAC-Auth")
|
||||
if hmac_header:
|
||||
expected_hmac = hmac.new(SECRET_KEY_HMAC.encode(), payload_user_id.encode(), hashlib.sha256).hexdigest()
|
||||
if not hmac.compare_digest(expected_hmac, hmac_header):
|
||||
db["security_logs"].append(f"[WARNING] HMAC mismatch for user {payload_user_id}!")
|
||||
return False, "Invalid HMAC (possible payload/identity tampering)"
|
||||
|
||||
return True, "Authenticated"
|
||||
|
||||
# AI verification simulation (Mocking Gemini API)
|
||||
def mock_gemini_verify_payment(invoice_number, amount, proof_text):
|
||||
print(f"[Gemini AI Processing] Verifying proof for Invoice {invoice_number} of amount {amount}...")
|
||||
print(f"[Gemini AI Input Proof] '{proof_text}'")
|
||||
# Mocking semantic check on proof text
|
||||
success = False
|
||||
reason = ""
|
||||
|
||||
proof_lower = proof_text.lower()
|
||||
amount_str = str(int(amount)) if amount == int(amount) else str(amount)
|
||||
|
||||
if amount_str in proof_lower and ("successful" in proof_lower or "transferred" in proof_lower or "تم تحويل" in proof_lower or "نجاح" in proof_lower):
|
||||
success = True
|
||||
reason = "Proof text verified successfully. Amount matches invoice."
|
||||
else:
|
||||
reason = f"Verification failed: Proof text does not indicate successful transfer of {amount}."
|
||||
|
||||
print(f"[Gemini AI Output] Verified: {success}, Reason: {reason}")
|
||||
return {"verified": success, "reason": reason}
|
||||
|
||||
# Atomic transaction simulation for ride payment
|
||||
def process_ride_payment(ride_id, driver_id, passenger_id, amount, payment_method, wallet_checked, s2s_key):
|
||||
# S2S auth check
|
||||
if s2s_key != S2S_SHARED_KEY:
|
||||
return {"status": "failure", "message": "Unauthorized S2S call"}
|
||||
|
||||
print(f"\n--- Database Transaction Initiated for Ride {ride_id} ---")
|
||||
|
||||
# Save a rollback point
|
||||
rollback_db = {
|
||||
"payments": list(db["payments"]),
|
||||
"passengerWallet": dict(db["passengerWallet"]),
|
||||
"driverWallet": dict(db["driverWallet"])
|
||||
}
|
||||
|
||||
try:
|
||||
# 1. Insert payment record
|
||||
final_method = payment_method + "Ride" if wallet_checked else payment_method
|
||||
payment_record = {
|
||||
"id": str(uuid.uuid4().int)[:15],
|
||||
"amount": amount,
|
||||
"payment_method": final_method,
|
||||
"passengerID": passenger_id,
|
||||
"rideId": ride_id,
|
||||
"driverID": driver_id
|
||||
}
|
||||
db["payments"].append(payment_record)
|
||||
print(f"[DB] Inserted payment record: {payment_record}")
|
||||
|
||||
# 2. Deduct from passenger wallet
|
||||
if wallet_checked:
|
||||
if passenger_id not in db["passengerWallet"]:
|
||||
db["passengerWallet"][passenger_id] = 0.0
|
||||
|
||||
db["passengerWallet"][passenger_id] -= amount
|
||||
print(f"[DB] Deducted {amount} from Passenger {passenger_id}. New balance: {db["passengerWallet"][passenger_id]}")
|
||||
|
||||
# Settle debt if balance was negative before (example scenario)
|
||||
# here we just apply deduction.
|
||||
|
||||
# 3. Deduct driver points (8% platform commission)
|
||||
commission = amount * 0.08
|
||||
if driver_id not in db["driverWallet"]:
|
||||
db["driverWallet"][driver_id] = 0.0
|
||||
db["driverWallet"][driver_id] -= commission
|
||||
print(f"[DB] Subtracted 8% commission ({commission}) from Driver {driver_id} points. New points balance: {db["driverWallet"][driver_id]}")
|
||||
|
||||
print(f"[DB] Transaction Committed successfully.")
|
||||
return {"status": "success", "message": "Transaction committed"}
|
||||
|
||||
except Exception as e:
|
||||
# Rollback
|
||||
db["payments"] = rollback_db["payments"]
|
||||
db["passengerWallet"] = rollback_db["passengerWallet"]
|
||||
db["driverWallet"] = rollback_db["driverWallet"]
|
||||
print(f"[DB ERROR] Transaction Failed. Rolled back changes. Error: {str(e)}")
|
||||
return {"status": "failure", "message": f"Transaction failed: {str(e)}"}
|
||||
|
||||
# Run Scenario Simulations
|
||||
def run_egypt_simulation():
|
||||
print("\n" + "="*50)
|
||||
print("SCENARIO 1: EGYPT - CREDIT CARD / PAYMOB (Ahmed)")
|
||||
print("="*50)
|
||||
user_id = "egypt_passenger_101"
|
||||
driver_id = "egypt_driver_501"
|
||||
device_fp = "ahmed_iphone_13_fp_hash"
|
||||
|
||||
init_user(user_id, 500.0) # passenger wallet balance
|
||||
init_user(driver_id, 100.0, is_driver=True) # driver wallet points
|
||||
|
||||
# 1. Client signs request
|
||||
jwt = generate_jwt(user_id, device_fp)
|
||||
hmac_header = calculate_hmac_header(user_id)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt}",
|
||||
"X-Device-FP": device_fp,
|
||||
"X-HMAC-Auth": hmac_header
|
||||
}
|
||||
|
||||
print(f"Passenger {user_id} initiating payment for Egypt ride. Ride cost: 120 EGP.")
|
||||
|
||||
# 2. Server authenticates headers
|
||||
auth_ok, msg = authenticate_request(headers, user_id)
|
||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
||||
|
||||
if auth_ok:
|
||||
# S2S transaction
|
||||
res = process_ride_payment(
|
||||
ride_id="ride_eg_999",
|
||||
driver_id=driver_id,
|
||||
passenger_id=user_id,
|
||||
amount=120.0,
|
||||
payment_method="PayMob",
|
||||
wallet_checked=True,
|
||||
s2s_key=S2S_SHARED_KEY
|
||||
)
|
||||
print(f"[Final Result] {res}")
|
||||
|
||||
def run_syria_simulation():
|
||||
print("\n" + "="*50)
|
||||
print("SCENARIO 2: SYRIA - MTN CASH WITH AI VERIFICATION (Bassel)")
|
||||
print("="*50)
|
||||
user_id = "syria_passenger_202"
|
||||
driver_id = "syria_driver_602"
|
||||
device_fp = "bassel_galaxy_s22_fp_hash"
|
||||
|
||||
init_user(user_id, 0.0) # passenger starts with 0.0
|
||||
init_user(driver_id, 50.0, is_driver=True) # driver starts with 50 points
|
||||
|
||||
jwt = generate_jwt(user_id, device_fp)
|
||||
hmac_header = calculate_hmac_header(user_id)
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt}",
|
||||
"X-Device-FP": device_fp,
|
||||
"X-HMAC-Auth": hmac_header
|
||||
}
|
||||
|
||||
invoice_number = "INV-MTN-8877"
|
||||
amount = 50000.0 # 50,000 SYP for the ride/wallet load
|
||||
|
||||
print(f"Passenger {user_id} loads wallet via MTN Cash for invoice {invoice_number} of {amount} SYP.")
|
||||
print("User transfers money and uploads transaction SMS proof.")
|
||||
|
||||
proof_text = "MTN Cash: You have successfully transferred 50000 SYP to SIRO system. Ref: 9812739182."
|
||||
|
||||
# 1. Server authenticates headers
|
||||
auth_ok, msg = authenticate_request(headers, user_id)
|
||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
||||
|
||||
if auth_ok:
|
||||
# 2. AI verifies proof text
|
||||
ai_res = mock_gemini_verify_payment(invoice_number, amount, proof_text)
|
||||
|
||||
if ai_res["verified"]:
|
||||
print(f"[Server] AI verified payment. Settle wallet.")
|
||||
# Atomic settlement
|
||||
print(f"\n--- Wallet Settlement Transaction Initiated (Syria) ---")
|
||||
db["passengerWallet"][user_id] += amount
|
||||
print(f"[DB] Added {amount} SYP to passenger {user_id} wallet. New balance: {db["passengerWallet"][user_id]} SYP")
|
||||
print("[DB] Transaction Committed.")
|
||||
else:
|
||||
print(f"[Server] Payment rejected: {ai_res['reason']}")
|
||||
|
||||
def run_jordan_simulation():
|
||||
print("\n" + "="*50)
|
||||
print("SCENARIO 3: JORDAN - CLIQ WITH TAMPERED DEVICE FINGERPRINT (Rania)")
|
||||
print("="*50)
|
||||
user_id = "jordan_passenger_303"
|
||||
device_fp = "rania_oneplus_11_fp_hash"
|
||||
tampered_fp = "hijacked_device_fp_hash" # Attacker trying to reuse Rania's token from a different device
|
||||
|
||||
# 1. Client token generated for Rania on her real device
|
||||
jwt = generate_jwt(user_id, device_fp)
|
||||
hmac_header = calculate_hmac_header(user_id)
|
||||
|
||||
# Attacker sends request with correct JWT but hijacked/different device fingerprint header
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt}",
|
||||
"X-Device-FP": tampered_fp, # Hijacked device header
|
||||
"X-HMAC-Auth": hmac_header
|
||||
}
|
||||
|
||||
print(f"Attacker attempts to trigger payment under Rania's user_id using hijacked token on different device.")
|
||||
|
||||
# 2. Server authenticates headers
|
||||
auth_ok, msg = authenticate_request(headers, user_id)
|
||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
||||
|
||||
if not auth_ok:
|
||||
print("[Server Alert] Attack blocked successfully! Session Hijacking thwarted.")
|
||||
print(f"Security logs: {db['security_logs']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_egypt_simulation()
|
||||
run_syria_simulation()
|
||||
run_jordan_simulation()
|
||||
@@ -1,56 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
def make_transparent_smooth(img_path, output_path, size):
|
||||
if not os.path.exists(img_path):
|
||||
print(f"Error: {img_path} does not exist.")
|
||||
return False
|
||||
|
||||
img = Image.open(img_path).convert("RGBA")
|
||||
img = img.resize(size, Image.Resampling.LANCZOS)
|
||||
|
||||
datas = img.getdata()
|
||||
new_data = []
|
||||
|
||||
for item in datas:
|
||||
r, g, b, a = item
|
||||
closeness = min(r, g, b)
|
||||
if closeness >= 240:
|
||||
if closeness >= 253:
|
||||
alpha = 0
|
||||
else:
|
||||
alpha = int(a * (253 - closeness) / (253 - 240))
|
||||
new_data.append((255, 255, 255, alpha))
|
||||
else:
|
||||
new_data.append(item)
|
||||
|
||||
img.putdata(new_data)
|
||||
img.save(output_path, "PNG")
|
||||
print(f"Successfully saved processed image to {output_path}")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
artifact_dir = "scratch"
|
||||
|
||||
|
||||
jobs = [
|
||||
("car_marker_normal_1781540915043.png", "siro_rider/assets/images/car.png", (80, 90)),
|
||||
("car_marker_normal_1781540915043.png", "siro_driver/assets/images/car.png", (80, 90)),
|
||||
("car_marker_lady_1781540926836.png", "siro_rider/assets/images/lady1.png", (80, 90)),
|
||||
("car_marker_lady_1781540926836.png", "siro_driver/assets/images/lady1.png", (80, 90)),
|
||||
("category_fixed_price_1781540942631.png", "siro_rider/assets/images/carspeed.png", (500, 500)),
|
||||
("category_comfort_1781540956914.png", "siro_rider/assets/images/blob.png", (500, 500)),
|
||||
("category_electric_1781540970352.png", "siro_rider/assets/images/electric.png", (500, 500)),
|
||||
("category_lady_1781540984745.png", "siro_rider/assets/images/lady.png", (500, 500)),
|
||||
]
|
||||
|
||||
|
||||
for src, dst, size in jobs:
|
||||
make_transparent_smooth(
|
||||
os.path.join(artifact_dir, src),
|
||||
dst,
|
||||
size
|
||||
)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 283 KiB |
@@ -1 +0,0 @@
|
||||
print("Hello from python script")
|
||||
@@ -1,81 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
languages = ['ar_eg', 'ar_jo', 'ar_sy', 'en', 'de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']
|
||||
local_dir = "siro_driver/lib/controller/local"
|
||||
|
||||
def parse_dart_map(filepath):
|
||||
translations = {}
|
||||
if not os.path.exists(filepath):
|
||||
print(f"Error: file {filepath} does not exist!")
|
||||
return None
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Matches: "key": "value"
|
||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
||||
matches = pattern.findall(content)
|
||||
for match in matches:
|
||||
key = match[1]
|
||||
val = match[3]
|
||||
# Store raw keys and values to check escaping
|
||||
translations[key] = val
|
||||
return translations
|
||||
|
||||
parsed_data = {}
|
||||
all_valid = True
|
||||
|
||||
for lang in languages:
|
||||
filepath = os.path.join(local_dir, f"{lang}.dart")
|
||||
trans = parse_dart_map(filepath)
|
||||
if trans is None:
|
||||
all_valid = False
|
||||
continue
|
||||
parsed_data[lang] = trans
|
||||
print(f"Verified {lang}.dart: parsed {len(trans)} entries.")
|
||||
if len(trans) != 2660:
|
||||
print(f" ERROR: Expected 2660 entries, got {len(trans)}")
|
||||
all_valid = False
|
||||
|
||||
if not all_valid:
|
||||
print("Verification FAILED on basic counts.")
|
||||
exit(1)
|
||||
|
||||
# Check that key sets are identical
|
||||
ref_lang = languages[0]
|
||||
ref_keys = set(parsed_data[ref_lang].keys())
|
||||
|
||||
for lang in languages[1:]:
|
||||
keys = set(parsed_data[lang].keys())
|
||||
if keys != ref_keys:
|
||||
print(f"ERROR: Key sets differ between {ref_lang} and {lang}!")
|
||||
print(f" Keys in {ref_lang} not in {lang}: {len(ref_keys - keys)}")
|
||||
print(f" Keys in {lang} not in {ref_lang}: {len(keys - ref_keys)}")
|
||||
all_valid = False
|
||||
|
||||
# Check for escaping errors (e.g. unescaped dollar signs in double-quoted strings in the Dart source code)
|
||||
# In the raw file content, any dollar sign must be preceded by a backslash unless it is already escaped.
|
||||
# Let's inspect the files directly for raw '$' characters that are not preceded by '\'
|
||||
dollar_pattern = re.compile(r'(?<!\\)\$')
|
||||
|
||||
for lang in languages:
|
||||
filepath = os.path.join(local_dir, f"{lang}.dart")
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
for idx, line in enumerate(lines):
|
||||
# We search inside map entries
|
||||
if ":" in line:
|
||||
# check for unescaped dollar signs
|
||||
# A dollar sign not preceded by a backslash is a compilation error in Dart double-quoted strings
|
||||
# unless it's single quotes or some specific construct, but we used double quotes for all lines.
|
||||
unescaped_dollars = dollar_pattern.findall(line)
|
||||
if unescaped_dollars:
|
||||
print(f"ERROR: Unescaped dollar sign in {filepath} line {idx+1}:")
|
||||
print(f" {line.strip()}")
|
||||
all_valid = False
|
||||
|
||||
if all_valid:
|
||||
print("\nSUCCESS: All files verified! They have identical keys (2660 keys) and correct escaping.")
|
||||
else:
|
||||
print("\nVerification FAILED.")
|
||||
exit(1)
|
||||
@@ -20,7 +20,7 @@
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
// Function to check for common root binaries
|
||||
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
@@ -29,7 +29,13 @@ bool isRooted()
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk"};
|
||||
"/sbin/magisk",
|
||||
"/data/adb/magisk/magiskinit",
|
||||
"/data/adb/magisk/magisk",
|
||||
"/data/adb/magisk.db",
|
||||
"/data/adb/ksu",
|
||||
"/data/adb/apatch",
|
||||
"/data/adb/ap/single"};
|
||||
|
||||
for (const auto &path : paths)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// import 'dart:io';
|
||||
|
||||
// import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
|
||||
class DeviceHelper {
|
||||
static Future<String> getDeviceFingerprint() async {
|
||||
@@ -80,7 +83,7 @@ class SecurityHelper {
|
||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Error during security checks: $e");
|
||||
Log.print("Error during security checks: $e");
|
||||
}
|
||||
|
||||
await box.write('isNotTrust', isNotTrust);
|
||||
@@ -98,7 +101,7 @@ class SecurityHelper {
|
||||
}
|
||||
|
||||
static void _showSecurityWarning() {
|
||||
RxInt secondsRemaining = 10.obs;
|
||||
final secondsRemaining = RxInt(10);
|
||||
|
||||
Get.dialog(
|
||||
CupertinoAlertDialog(
|
||||
@@ -150,90 +153,3 @@ class SecurityHelper {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// class DeviceInfoPlus {
|
||||
// static List<Map<String, dynamic>> deviceDataList = [];
|
||||
|
||||
// static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
|
||||
// final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
|
||||
// try {
|
||||
// if (Platform.isAndroid) {
|
||||
// AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
// Map<String, dynamic> deviceData = {
|
||||
// 'platform': 'Android',
|
||||
// 'brand': androidInfo.brand,
|
||||
// 'model': androidInfo.model,
|
||||
// 'androidId': androidInfo.device,
|
||||
// 'versionRelease': androidInfo.version.release,
|
||||
// 'sdkVersion': androidInfo.version.sdkInt,
|
||||
// 'manufacturer': androidInfo.manufacturer,
|
||||
// 'isPhysicalDevice': androidInfo.isPhysicalDevice,
|
||||
// 'serialNumber': androidInfo.serialNumber,
|
||||
// 'fingerprint': androidInfo.fingerprint,
|
||||
// 'type': androidInfo.type,
|
||||
// 'data': androidInfo.data,
|
||||
// 'version': androidInfo.version,
|
||||
// 'tags': androidInfo.tags,
|
||||
// 'display': androidInfo.display,
|
||||
// };
|
||||
// deviceDataList.add(deviceData);
|
||||
// } else if (Platform.isIOS) {
|
||||
// IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
|
||||
// Map<String, dynamic> deviceData = {
|
||||
// 'brand': 'Apple',
|
||||
// 'model': iosInfo.model,
|
||||
// 'systemName': iosInfo.systemName,
|
||||
// 'systemVersion': iosInfo.systemVersion,
|
||||
// 'utsname': iosInfo.utsname,
|
||||
// 'isPhysicalDevice': iosInfo.isPhysicalDevice,
|
||||
// 'identifierForVendor': iosInfo.identifierForVendor,
|
||||
// 'name': iosInfo.name,
|
||||
// 'localizedModel': iosInfo.localizedModel,
|
||||
// };
|
||||
// deviceDataList.add(deviceData);
|
||||
// } else if (Platform.isMacOS) {
|
||||
// MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
|
||||
// Map<String, dynamic> deviceData = {
|
||||
// 'platform': 'macOS',
|
||||
// 'model': macInfo.model,
|
||||
// 'version': macInfo.systemGUID,
|
||||
// };
|
||||
// deviceDataList.add(deviceData);
|
||||
// } else if (Platform.isWindows) {
|
||||
// WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
|
||||
// Map<String, dynamic> deviceData = {
|
||||
// 'platform': 'Windows',
|
||||
// 'manufacturer': windowsInfo.computerName,
|
||||
// 'version': windowsInfo.majorVersion,
|
||||
// 'deviceId': windowsInfo.deviceId,
|
||||
// 'userName': windowsInfo.userName,
|
||||
// 'productName': windowsInfo.productName,
|
||||
// 'installDate': windowsInfo.installDate,
|
||||
// 'productId': windowsInfo.productId,
|
||||
// 'numberOfCores': windowsInfo.numberOfCores,
|
||||
// 'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
|
||||
// };
|
||||
// deviceDataList.add(deviceData);
|
||||
// } else if (Platform.isLinux) {
|
||||
// LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
|
||||
// Map<String, dynamic> deviceData = {
|
||||
// 'platform': 'Linux',
|
||||
// 'manufacturer': linuxInfo.name,
|
||||
// 'version': linuxInfo.version,
|
||||
// };
|
||||
// deviceDataList.add(deviceData);
|
||||
// }
|
||||
// } catch (e) {
|
||||
// }
|
||||
|
||||
// return deviceDataList;
|
||||
// }
|
||||
|
||||
// // Method to print all device data
|
||||
// static void printDeviceInfo() {
|
||||
// for (Map<String, dynamic> deviceData in deviceDataList) {
|
||||
// 'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to check security status: ${e.message}");
|
||||
return true; // Treat platform errors as a compromised device (for safety)
|
||||
return false; // Platform not supported → treat as secure
|
||||
} catch (e) {
|
||||
print("Security check error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart' as http_io;
|
||||
|
||||
class SslPinning {
|
||||
SslPinning._();
|
||||
@@ -29,13 +30,13 @@ class SslPinning {
|
||||
(X509Certificate cert, String host, int port) {
|
||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||
for (final entry in _pins.entries) {
|
||||
if (host.endsWith(entry.key)) {
|
||||
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||
if (entry.value.contains(derHash)) return true;
|
||||
}
|
||||
}
|
||||
if (_globalPins.contains(derHash)) return true;
|
||||
return false;
|
||||
};
|
||||
return http.IOClient(httpClient);
|
||||
return http_io.IOClient(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
@@ -13,6 +13,8 @@ import firebase_messaging
|
||||
import flutter_image_compress_macos
|
||||
import flutter_secure_storage_macos
|
||||
import local_auth_darwin
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
|
||||
@@ -25,6 +27,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
PODS:
|
||||
- AppAuth (1.7.6):
|
||||
- AppAuth/Core (= 1.7.6)
|
||||
- AppAuth/ExternalUserAgent (= 1.7.6)
|
||||
- AppAuth/Core (1.7.6)
|
||||
- AppAuth/ExternalUserAgent (1.7.6):
|
||||
- AppAuth/Core
|
||||
- AppCheckCore (11.2.0):
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
@@ -80,20 +70,9 @@ PODS:
|
||||
- flutter_secure_storage_macos (6.1.3):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- google_sign_in_ios (0.0.1):
|
||||
- AppAuth (>= 1.7.4)
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleSignIn (~> 8.0)
|
||||
- GTMSessionFetcher (>= 3.4.0)
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleSignIn (8.0.0):
|
||||
- AppAuth (< 2.0, >= 1.7.3)
|
||||
- AppCheckCore (~> 11.0)
|
||||
- GTMAppAuth (< 5.0, >= 4.1.1)
|
||||
- GTMSessionFetcher/Core (~> 3.3)
|
||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
@@ -118,14 +97,6 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMAppAuth (4.1.1):
|
||||
- AppAuth/Core (~> 1.7)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
|
||||
- GTMSessionFetcher (3.5.0):
|
||||
- GTMSessionFetcher/Full (= 3.5.0)
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- GTMSessionFetcher/Full (3.5.0):
|
||||
- GTMSessionFetcher/Core
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -134,6 +105,9 @@ PODS:
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- PromisesSwift (2.4.0):
|
||||
- PromisesObjC (= 2.4.0)
|
||||
@@ -152,15 +126,13 @@ DEPENDENCIES:
|
||||
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
|
||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- google_sign_in_ios (from `Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin`)
|
||||
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- Firebase
|
||||
- FirebaseCore
|
||||
- FirebaseCoreExtension
|
||||
@@ -171,10 +143,7 @@ SPEC REPOS:
|
||||
- FirebaseRemoteConfigInterop
|
||||
- FirebaseSessions
|
||||
- GoogleDataTransport
|
||||
- GoogleSignIn
|
||||
- GoogleUtilities
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- PromisesSwift
|
||||
@@ -196,20 +165,18 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
google_sign_in_ios:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin
|
||||
local_auth_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
|
||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
||||
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
|
||||
@@ -224,19 +191,16 @@ SPEC CHECKSUMS:
|
||||
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
|
||||
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
|
||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "93.0.0"
|
||||
version: "88.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -21,10 +21,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.1"
|
||||
version: "8.1.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -53,10 +53,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -117,10 +117,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -145,14 +145,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -186,7 +178,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
@@ -205,10 +197,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
||||
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.7"
|
||||
version: "3.1.2"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -253,18 +245,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: envied
|
||||
sha256: "42132a746494b0a7bc19062cdddd3a01694f696caca684456ff01526c833decc"
|
||||
sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.1"
|
||||
envied_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: envied_generator
|
||||
sha256: e1e66498080f531e89d9ea7971f96b287dffdd05df16efdd31f9f74faa77e005
|
||||
sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.1"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -309,10 +301,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
version: "0.9.4+4"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -490,10 +482,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||
sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.35"
|
||||
version: "2.0.31"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -588,10 +580,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
version: "6.3.2"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -600,14 +592,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -636,10 +620,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
||||
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.1"
|
||||
version: "4.8.0"
|
||||
image_cropper:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -668,18 +652,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
||||
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+17"
|
||||
version: "0.8.13+1"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -692,10 +676,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
version: "0.8.13"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -708,10 +692,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
version: "0.2.2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -752,22 +736,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0+1"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
jni_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni_flutter
|
||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -780,10 +748,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
version: "4.9.0"
|
||||
jwt_decoder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -804,26 +772,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -852,18 +820,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467
|
||||
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.56"
|
||||
version: "1.0.52"
|
||||
local_auth_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_darwin
|
||||
sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
|
||||
sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.1"
|
||||
version: "1.6.0"
|
||||
local_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -900,26 +868,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.16.0"
|
||||
mgrs_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -936,14 +904,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -952,6 +912,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -972,18 +948,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
||||
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.2.19"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.4.2"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1096,14 +1072,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
record_use:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_use
|
||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
secure_string_operations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1136,42 +1104,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
||||
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
version: "4.2.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+1"
|
||||
version: "2.4.2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+3"
|
||||
version: "2.4.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
||||
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.8"
|
||||
version: "2.5.6"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1224,10 +1192,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
||||
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0+1"
|
||||
version: "3.4.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1240,10 +1208,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1272,18 +1240,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.30"
|
||||
version: "6.3.20"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
version: "6.3.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1296,10 +1264,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
version: "3.2.3"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1312,10 +1280,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
version: "2.4.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1328,18 +1296,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1408,10 +1376,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1421,5 +1389,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.11.0 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
||||
@@ -67,6 +67,7 @@ dependencies:
|
||||
device_info_plus: ^11.5.0
|
||||
flutter_staggered_animations: ^1.1.1
|
||||
jailbreak_root_detection: ^1.1.5
|
||||
package_info_plus: ^4.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
// Function to check for common root binaries
|
||||
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
@@ -29,7 +29,13 @@ bool isRooted()
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk"};
|
||||
"/sbin/magisk",
|
||||
"/data/adb/magisk/magiskinit",
|
||||
"/data/adb/magisk/magisk",
|
||||
"/data/adb/magisk.db",
|
||||
"/data/adb/ksu",
|
||||
"/data/adb/apatch",
|
||||
"/data/adb/ap/single"};
|
||||
|
||||
for (const auto &path : paths)
|
||||
{
|
||||
|
||||
@@ -132,21 +132,19 @@ class CRUD {
|
||||
try {
|
||||
attempts++;
|
||||
response = await doPost();
|
||||
break; // نجح الاتصال — نخرج
|
||||
break;
|
||||
} on SocketException catch (_) {
|
||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||
if (attempts >= 3) {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||
return 'no_internet';
|
||||
}
|
||||
// انتظار قبل إعادة المحاولة — مهم للشبكات المتقطعة
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
await Future.delayed(Duration(seconds: attempts));
|
||||
} on TimeoutException catch (_) {
|
||||
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||
if (attempts >= 3) return 'failure';
|
||||
// لا انتظار — نعيد فوراً
|
||||
await Future.delayed(Duration(milliseconds: 500 * attempts));
|
||||
} catch (e) {
|
||||
// errno = 9 (Bad file descriptor) — إعادة المحاولة
|
||||
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
continue;
|
||||
|
||||
@@ -301,7 +301,7 @@ class SecurityHelper {
|
||||
// }
|
||||
static void _showSecurityWarning() {
|
||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||
RxInt secondsRemaining = 10.obs;
|
||||
final secondsRemaining = RxInt(10);
|
||||
|
||||
Get.dialog(
|
||||
CupertinoAlertDialog(
|
||||
|
||||
@@ -16,7 +16,10 @@ class SecurityChecks {
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to check security status: ${e.message}");
|
||||
return true; // Treat platform errors as a compromised device (for safety)
|
||||
return false; // Platform not supported → treat as secure
|
||||
} catch (e) {
|
||||
print("Security check error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart' as http_io;
|
||||
|
||||
class SslPinning {
|
||||
SslPinning._();
|
||||
@@ -29,13 +30,13 @@ class SslPinning {
|
||||
(X509Certificate cert, String host, int port) {
|
||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||
for (final entry in _pins.entries) {
|
||||
if (host.endsWith(entry.key)) {
|
||||
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||
if (entry.value.contains(derHash)) return true;
|
||||
}
|
||||
}
|
||||
if (_globalPins.contains(derHash)) return true;
|
||||
return false;
|
||||
};
|
||||
return http.IOClient(httpClient);
|
||||
return http_io.IOClient(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
// Function to check for common root binaries
|
||||
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
@@ -28,7 +28,13 @@ bool isRooted()
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk"};
|
||||
"/sbin/magisk",
|
||||
"/data/adb/magisk/magiskinit",
|
||||
"/data/adb/magisk/magisk",
|
||||
"/data/adb/magisk.db",
|
||||
"/data/adb/ksu",
|
||||
"/data/adb/apatch",
|
||||
"/data/adb/ap/single"};
|
||||
|
||||
for (const auto &path : paths)
|
||||
{
|
||||
|
||||
@@ -24,16 +24,35 @@ class CRUD {
|
||||
final NetGuard _netGuard = NetGuard();
|
||||
final _client = SslPinning.createPinnedClient();
|
||||
|
||||
/// Stores the signature of the last logged error to prevent duplicates.
|
||||
static bool _isRefreshingJWT = false;
|
||||
static String _lastErrorSignature = '';
|
||||
|
||||
/// Stores the timestamp of the last logged error.
|
||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||
|
||||
/// The minimum time that must pass before logging the same error again.
|
||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||
|
||||
/// Asynchronously logs an error to the server with debouncing to prevent log flooding.
|
||||
/// JWT validity check without external libraries.
|
||||
static bool _isJwtValid(String? token) {
|
||||
if (token == null || token.isEmpty) return false;
|
||||
try {
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) return false;
|
||||
String payload = parts[1];
|
||||
switch (payload.length % 4) {
|
||||
case 2:
|
||||
payload += '==';
|
||||
break;
|
||||
case 3:
|
||||
payload += '=';
|
||||
break;
|
||||
}
|
||||
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||
final exp = decoded['exp'];
|
||||
if (exp == null) return false;
|
||||
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> addError(
|
||||
String error, String details, String where) async {
|
||||
try {
|
||||
@@ -54,11 +73,9 @@ class CRUD {
|
||||
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||
|
||||
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
|
||||
Log.print(
|
||||
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||
|
||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
||||
CRUD().post(
|
||||
link: AppLink.addError,
|
||||
payload: {
|
||||
@@ -75,22 +92,14 @@ class CRUD {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
String _getFpHeader() {
|
||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
|
||||
// بدلاً من GetStorage (غير مشفر)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Future<String> _getJwt() async {
|
||||
try {
|
||||
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
||||
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
||||
// Fallback إلى GetStorage للتوافقية
|
||||
final String? fallback = box.read(BoxName.jwt);
|
||||
if (fallback != null) {
|
||||
return r(fallback).toString().split(Env.addd)[0];
|
||||
@@ -100,7 +109,6 @@ class CRUD {
|
||||
return r(encryptedJwt).toString().split(Env.addd)[0];
|
||||
} catch (e) {
|
||||
Log.print('Error reading JWT from SecureStorage: $e');
|
||||
// Fallback
|
||||
final String? fallback = box.read(BoxName.jwt);
|
||||
if (fallback != null) {
|
||||
return r(fallback).toString().split(Env.addd)[0];
|
||||
@@ -109,168 +117,119 @@ class CRUD {
|
||||
}
|
||||
}
|
||||
|
||||
/// Centralized private method to handle all API requests.
|
||||
/// Includes retry logic, network checking, and standardized error handling.
|
||||
/// Centralized request handler with retry for weak networks.
|
||||
/// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
|
||||
Future<dynamic> _makeRequest({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
const connectTimeout = Duration(seconds: 6);
|
||||
const receiveTimeout = Duration(seconds: 10);
|
||||
const totalTimeout = Duration(seconds: 60);
|
||||
|
||||
Future<http.Response> doPost() {
|
||||
final url = Uri.parse(link);
|
||||
return _client
|
||||
.post(url, body: payload, headers: headers)
|
||||
.timeout(connectTimeout + receiveTimeout);
|
||||
.timeout(totalTimeout);
|
||||
}
|
||||
|
||||
http.Response response;
|
||||
try {
|
||||
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
|
||||
http.Response? response;
|
||||
int attempts = 0;
|
||||
|
||||
while (attempts < 3) {
|
||||
try {
|
||||
attempts++;
|
||||
response = await doPost();
|
||||
break;
|
||||
} on SocketException catch (_) {
|
||||
response = await doPost();
|
||||
} on TimeoutException catch (_) {
|
||||
response = await doPost();
|
||||
}
|
||||
|
||||
final sc = response.statusCode;
|
||||
final body = response.body;
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: $body');
|
||||
// Log.print('link: $link');
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
// 2xx
|
||||
if (sc >= 200 && sc < 300) {
|
||||
try {
|
||||
final jsonData = jsonDecode(body);
|
||||
return jsonData;
|
||||
} catch (e, st) {
|
||||
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||
'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||
if (attempts >= 3) {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||
return 'no_internet';
|
||||
}
|
||||
await Future.delayed(Duration(seconds: attempts));
|
||||
} on TimeoutException catch (_) {
|
||||
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||
if (attempts >= 3) return 'failure';
|
||||
} catch (e) {
|
||||
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 401 → تجديد التوكن تلقائياً
|
||||
if (sc == 401) {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
return 'token_expired';
|
||||
}
|
||||
|
||||
// 5xx
|
||||
if (sc >= 500) {
|
||||
addError(
|
||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
// 4xx أخرى
|
||||
return 'failure';
|
||||
} on SocketException {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||
return 'no_internet';
|
||||
} on TimeoutException {
|
||||
return 'failure';
|
||||
} catch (e, st) {
|
||||
addError('HTTP Request Exception: $e', 'Stack: $st',
|
||||
'CRUD._makeRequest $link');
|
||||
if (response == null) return 'failure';
|
||||
|
||||
final sc = response.statusCode;
|
||||
final body = response.body;
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: $body');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
if (sc >= 200 && sc < 300) {
|
||||
try {
|
||||
return jsonDecode(body);
|
||||
} catch (e, st) {
|
||||
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||
'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == 401) {
|
||||
final isNonCritical = link.contains('errorApp.php');
|
||||
if (!_isRefreshingJWT && !isNonCritical) {
|
||||
_isRefreshingJWT = true;
|
||||
try {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
} finally {
|
||||
_isRefreshingJWT = false;
|
||||
}
|
||||
}
|
||||
return 'token_expired';
|
||||
}
|
||||
|
||||
if (sc >= 500) {
|
||||
addError(
|
||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// post — طلب POST عادي للراكب/السائق
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// القيمة: fp_encrypted من GetStorage
|
||||
// السيرفر يتحقق: sha256(fp_encrypted + FP_PEPPER) == JWT.fingerPrint
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> post({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
final token = await _getJwt();
|
||||
String token = await _getJwt();
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
return await _makeRequest(
|
||||
link: link,
|
||||
payload: payload,
|
||||
headers: headers,
|
||||
);
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// get — طلب GET للراكب/السائق (يستخدم POST method)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> get({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
final token = await _getJwt();
|
||||
var url = Uri.parse(link);
|
||||
var response = await _client.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
String token = await _getJwt();
|
||||
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: ${response.body}');
|
||||
Log.print('payload: $payload');
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return response.body;
|
||||
} else if (response.statusCode == 401) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['error'] == 'Token expired') {
|
||||
print("CRUD.get: Token expired, refreshing and retrying once...");
|
||||
await Get.put(LoginController()).getJWT();
|
||||
|
||||
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
|
||||
var retryResponse = await _client.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization':
|
||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
},
|
||||
);
|
||||
|
||||
if (retryResponse.statusCode == 200) {
|
||||
return retryResponse.body;
|
||||
}
|
||||
return jsonEncode(
|
||||
{'status': 'failure', 'message': 'token_expired_retry_failed'});
|
||||
} else {
|
||||
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
|
||||
}
|
||||
} else {
|
||||
addError('Non-200 response code: ${response.statusCode}',
|
||||
'crud().get - Other', url.toString());
|
||||
return jsonEncode({
|
||||
'status': 'failure',
|
||||
'message': 'server_error_${response.statusCode}'
|
||||
});
|
||||
}
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
@@ -290,65 +249,30 @@ class CRUD {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $jwt',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
// add print debug
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
Log.print('link: $link');
|
||||
|
||||
return await _makeRequest(
|
||||
link: link,
|
||||
payload: payload,
|
||||
headers: headers,
|
||||
);
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// getWallet — طلب GET لسيرفر المدفوعات (يستخدم POST method)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> getWallet({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var s = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
var url = Uri.parse(link);
|
||||
|
||||
var response = await _client.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'success') {
|
||||
return response.body;
|
||||
}
|
||||
return jsonData['status'];
|
||||
} else if (response.statusCode == 401) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['error'] == 'Token expired') {
|
||||
await Get.put(LoginController()).getJwtWallet();
|
||||
return 'token_expired';
|
||||
} else {
|
||||
addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401',
|
||||
url.toString());
|
||||
return 'failure';
|
||||
}
|
||||
} else {
|
||||
addError('Non-200 response code: ${response.statusCode}',
|
||||
'crud().getWallet - Other', url.toString());
|
||||
return 'failure';
|
||||
}
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
@@ -361,65 +285,20 @@ class CRUD {
|
||||
{required String link, Map<String, dynamic>? payload}) async {
|
||||
final s = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
final url = Uri.parse(link);
|
||||
|
||||
try {
|
||||
final response = await _client.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
Map<String, dynamic> wrap(String status, {Object? message, int? code}) {
|
||||
return {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'code': code ?? response.statusCode,
|
||||
};
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
try {
|
||||
return jsonDecode(response.body);
|
||||
} catch (e) {
|
||||
return wrap('failure',
|
||||
message: 'JSON decode error', code: response.statusCode);
|
||||
}
|
||||
} else if (response.statusCode == 401) {
|
||||
try {
|
||||
final jsonData = jsonDecode(response.body);
|
||||
if (jsonData is Map && jsonData['error'] == 'Token expired') {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
return {
|
||||
'status': 'failure',
|
||||
'message': 'token_expired',
|
||||
'code': 401
|
||||
};
|
||||
}
|
||||
return wrap('failure', message: jsonData);
|
||||
} catch (_) {
|
||||
return wrap('failure', message: response.body);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
final jsonData = jsonDecode(response.body);
|
||||
return wrap('failure', message: jsonData);
|
||||
} catch (_) {
|
||||
return wrap('failure', message: response.body);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
'status': 'failure',
|
||||
'message': 'HTTP request error: $e',
|
||||
'code': -1
|
||||
};
|
||||
final result = await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
if (result is Map || result is List) return result;
|
||||
if (result == 'no_internet') {
|
||||
return {'status': 'failure', 'message': 'no_internet', 'code': -1};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future sendWhatsAppAuth(String to, String token) async {
|
||||
|
||||
@@ -254,7 +254,7 @@ class SecurityHelper {
|
||||
/// Deletes all app data
|
||||
static void _showSecurityWarning() {
|
||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||
RxInt secondsRemaining = 10.obs;
|
||||
final secondsRemaining = RxInt(10);
|
||||
|
||||
Get.dialog(
|
||||
CupertinoAlertDialog(
|
||||
|
||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
Log.print("Failed to check security status: ${e.message}");
|
||||
return true; // Treat platform errors as a compromised device (for safety)
|
||||
return false; // Platform not supported → treat as secure (not compromised)
|
||||
} catch (e) {
|
||||
Log.print("Security check error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart' as http_io;
|
||||
|
||||
class SslPinning {
|
||||
SslPinning._();
|
||||
@@ -29,13 +30,13 @@ class SslPinning {
|
||||
(X509Certificate cert, String host, int port) {
|
||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||
for (final entry in _pins.entries) {
|
||||
if (host.endsWith(entry.key)) {
|
||||
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||
if (entry.value.contains(derHash)) return true;
|
||||
}
|
||||
}
|
||||
if (_globalPins.contains(derHash)) return true;
|
||||
return false;
|
||||
};
|
||||
return http.IOClient(httpClient);
|
||||
return http_io.IOClient(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
// Function to check for common root binaries
|
||||
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
@@ -29,7 +29,13 @@ bool isRooted()
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk"};
|
||||
"/sbin/magisk",
|
||||
"/data/adb/magisk/magiskinit",
|
||||
"/data/adb/magisk/magisk",
|
||||
"/data/adb/magisk.db",
|
||||
"/data/adb/ksu",
|
||||
"/data/adb/apatch",
|
||||
"/data/adb/ap/single"};
|
||||
|
||||
for (const auto &path : paths)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart' as http_io;
|
||||
|
||||
class SslPinning {
|
||||
SslPinning._();
|
||||
@@ -29,13 +30,13 @@ class SslPinning {
|
||||
(X509Certificate cert, String host, int port) {
|
||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||
for (final entry in _pins.entries) {
|
||||
if (host.endsWith(entry.key)) {
|
||||
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||
if (entry.value.contains(derHash)) return true;
|
||||
}
|
||||
}
|
||||
if (_globalPins.contains(derHash)) return true;
|
||||
return false;
|
||||
};
|
||||
return http.IOClient(httpClient);
|
||||
return http_io.IOClient(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
@@ -13,6 +13,7 @@ import firebase_messaging
|
||||
import flutter_image_compress_macos
|
||||
import flutter_local_notifications
|
||||
import flutter_secure_storage_macos
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
@@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
|
||||
@@ -5,10 +5,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "93.0.0"
|
||||
version: "88.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -21,10 +21,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.1"
|
||||
version: "8.1.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -53,10 +53,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -117,10 +117,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -145,14 +145,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -197,18 +189,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
version: "1.0.8"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
||||
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.7"
|
||||
version: "3.1.2"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -245,18 +237,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: envied
|
||||
sha256: "42132a746494b0a7bc19062cdddd3a01694f696caca684456ff01526c833decc"
|
||||
sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.1"
|
||||
envied_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: envied_generator
|
||||
sha256: e1e66498080f531e89d9ea7971f96b287dffdd05df16efdd31f9f74faa77e005
|
||||
sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.1"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -301,10 +293,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
version: "0.9.4+4"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -506,10 +498,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||
sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.35"
|
||||
version: "2.0.31"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -596,10 +588,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
version: "6.3.2"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -608,14 +600,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -676,18 +660,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
||||
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+17"
|
||||
version: "0.8.13+1"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -700,10 +684,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
version: "0.8.13"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -716,10 +700,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
version: "0.2.2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -752,22 +736,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
jni_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni_flutter
|
||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -780,10 +748,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
version: "4.9.0"
|
||||
jwt_decoder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -796,26 +764,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -836,26 +804,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -864,14 +832,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -900,18 +860,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
||||
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.2.19"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
version: "2.4.2"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1056,14 +1016,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
record_use:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_use
|
||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
secure_string_operations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1112,42 +1064,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
||||
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
version: "4.2.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+1"
|
||||
version: "2.4.2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+3"
|
||||
version: "2.4.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
||||
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.8"
|
||||
version: "2.5.6"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1200,10 +1152,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
||||
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0+1"
|
||||
version: "3.4.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1216,10 +1168,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.4"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1248,18 +1200,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.30"
|
||||
version: "6.3.20"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
version: "6.3.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1272,10 +1224,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
version: "3.2.3"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1288,10 +1240,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
version: "2.4.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1312,10 +1264,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.1.4"
|
||||
vibration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1336,10 +1288,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1413,5 +1365,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.11.0 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
||||
@@ -14,7 +14,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flutter_local_notifications_windows
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||