# خارطة تنفيذ SportPath والبنية التشغيلية تاريخ التحديث: 4 أكتوبر 2026 هذه الوثيقة تتمّم [خطة المنتج والبحث](FITNESS_PRODUCT_PLAN_AR.md)، وتحوّل طلب تسجيل الدخول والهاتف والسيرفر والنشر إلى تصميم تنفيذي. لا يوجد مستودع Git أو نطاق أو بيانات CloudPanel مهيأة بعد، لذلك سكربت النشر يجهّز طريقة العمل ولا يتصل بسيرفر الآن. ## المنتج الذي سنكمله نسخة عربية من SportPath على تطبيق iOS وAndroid، وموقع ويب متجاوب على نطاق المستخدم. يتشارك التطبيق والموقع خدمة PHP API وقاعدة MySQL ومكتبة المحتوى. يبقى SQLite للتسجيل المحلي والعمل دون إنترنت. يتضمن المنتج تسجيل رقم الهاتف، OTP عبر مزود الرسائل الذي يستخدمه المستخدم في تطبيقاته الأخرى، برامج أسبوعية، مكتبة تمارين GIF، تسجيل الطعام من صورة مع تصحيح المستخدم، قياسات تقدم، وإشعارات مناسبة للخطة. الموقع يشمل واجهة المستخدم الأساسية وإدارة المحتوى والحسابات بصلاحيات منفصلة. إذا أظهر التنفيذ أن واجهة الويب المشتركة توسع المشروع كثيرًا، نطلق لوحة الإدارة والصفحات الأساسية أولًا ثم نكمل تجربة الويب للمستخدم من نفس API. اتجاه التصميم: واجهة عربية RTL واضحة، أزرار كبيرة وتباين جيد ورسوم GIF سهلة التوقف، مع خط النظام في أجهزة Apple وبدائل النظام المتاحة على بقية الأجهزة. جميع النصوص والجداول والحقول تستخدم عائلة الخط نفسها مع تباعد مريح؛ لا نضمّن ملفات خطوط Apple في الموقع. ## تسجيل الهاتف والجلسات ### تسجيل مستخدم جديد 1. يكتب المستخدم رقم الهاتف بصيغة دولية E.164، ونوحد الأرقام قبل البحث أو التخزين. 2. ينشئ الخادم تحدي OTP عشوائيًا قصير الصلاحية، ويحفظ بصمته المشفرة والعدد المتبقي للمحاولات ووقت انتهاء الصلاحية، ثم يرسل الرمز عبر محول مزود OTP. 3. يثبت التطبيق الرمز. الخادم يتحقق من الصلاحية، وعدد المحاولات، وحدود الإرسال حسب رقم الهاتف وعنوان الشبكة والجهاز، ثم يستهلك التحدي مرة واحدة. 4. ينشئ الخادم المستخدم بعد إثبات الرقم، أو يستعيد الحساب الموجود حسب مسار الدخول. ردود إرسال OTP عامة حتى لا تكشف وجود رقم في قاعدة المستخدمين. 5. يصدر الخادم Access Token قصير الأجل وRefresh Token عشوائي طويل نسبيًا. Refresh token يخزن في قاعدة البيانات على هيئة hash، ويدوّر بعد كل استخدام، مع إمكانية إبطال الجلسة أو كل الجلسات. الرمز لا يُخزن نصًا صريحًا ولا يسجل في السجلات. الرسائل تتضمن اسم التطبيق ورمزًا قصير الأجل ومعدل إرسال محدود. تغيير رقم الهاتف يحتاج إثبات الرقم الجديد ومراجعة الجلسات النشطة. لا نضع JWT أو سر OTP ثابتًا داخل Flutter؛ مفتاح إرسال SMS ومفاتيح التوقيع تبقى على الخادم. ### محول مزود الرسائل نستخدم واجهة داخل PHP مثل `OtpProviderInterface` وعقدًا موثقًا لاستدعاء مزود المستخدم الحالي. إعدادات مثل اسم الحساب والسر والقالب ورقم المرسل تكون environment variables؛ اختيار المزود يتم من إعداد سري على الخادم. مزود الرسائل الذي ذكره المستخدم لم يتحدد اسمه تقنيًا بعد، لذا لا نكتب تكاملًا على تخمين اسم المنتج. الاختبار الأول يستخدم بيئة sandbox أو رقم اختبار من المزود، مع تعطيل إرسال حقيقي افتراضيًا في التطوير. ### ربط الحساب بالجهاز والبصمة الحيوية المقصود بالبصمة في المنتج هو أن يثبت Face ID أو Touch ID أو بصمة Android أن صاحب الحساب حاضر لفتح التطبيق أو توقيع طلب محلي. التطبيق لا يقرأ ولا يرسل صورة البصمة أو بيانات الوجه؛ نظام التشغيل يتحقق منها. عند أول تسجيل دخول، ينشئ التطبيق UUID عشوائيًا للتثبيت ومفتاحًا خاصًا غير قابل للتصدير داخل Keychain/Secure Enclave على iOS أو Android Keystore. يرسل المفتاح العام للخادم ويربطه بجلسة الجهاز. عند تفعيل الفتح الحيوي، يطلب النظام المصادقة قبل استخدام المفتاح/سر الجلسة، مع بديل رمز الجهاز أو OTP وإمكانية إلغاء هذا الجهاز من الحساب. لا نعتمد على serial/IMEI أو UDID ثابت لتحديد المستخدم. Android يقيد أرقام الأجهزة الدائمة للتطبيقات العادية، و`identifierForVendor` في iOS قد يتغير بعد حذف تطبيقات المورد وإعادة تثبيتها. معرّف التثبيت UUID قابل لإعادة الإنشاء، والمفتاح الخاص هو دليل امتلاك الجهاز. هذه الطريقة تمنع جمع هوية عتادية غير لازمة وتعمل عبر المنصتين. ## إعدادات الخادم والتحكم المركزي كلمات المرور ومفاتيح SMS وAI وJWT وقاعدة البيانات أسرار تشغيل ولا تُخزن في جداول إعدادات قابلة للقراءة من لوحة الإدارة. تحفظ في بيئة PHP التي يتيحها CloudPanel أو ملف أسرار خارج web root بصلاحية قراءة لمستخدم الموقع فقط. الخادم يرسل إعدادات عامة قابلة للتغيير دون إصدار تطبيق، مثل نسخة المحتوى، تفعيل ميزة تجريبية، حد رفع الصورة، الحد الأقصى للتذكيرات التي يقترحها التطبيق، روابط الدعم، وتوفر تسجيل الطعام أو أنواع الجلسات. يسجل كل إعداد: النوع، النطاق، تاريخ البداية، من عدله، ونسخة التغيير. لا يتلقى التطبيق مطلقًا مفاتيح مزود الرسائل أو كلمات مرور MySQL أو أسرار توقيع JWT. لوحة الإدارة خلف تسجيل دخول وصلاحية دورية. الأدوار المقترحة: مالك، مدير محتوى، دعم، ومستخدم. عمليات تعديل البرامج والمحتوى والإعدادات تسجل في audit log؛ إعدادات الخطة المنشورة ذات نسخة ثابتة حتى لا تتغير جلسات سابقة. بدأ التنفيذ محليًا: `backend/Config.php` يقرأ environment من ملف خارج `public/` أو من مسار `APP_ENV_FILE`، و`backend/Database.php` يأخذ بيانات MySQL من البيئة بدل القيم الثابتة. مخطط التثبيت الجديد يتضمن الهاتف والجلسات والأجهزة، وتوجد ترحيلات `backend/migrations/001_phone_auth_and_sessions.sql` و`002_workout_idempotency.sql` لقواعد البيانات القديمة. لم تُطبق الترحيلات على قاعدة فعلية؛ يلزم أخذ نسخة احتياطية ومراجعة مخطط الخادم قبل تطبيقها. عالجت بدايةً الحفظ المحلي: قاعدة SQLite الآن تجهز المفاتيح الأجنبية، وحفظ جلسة GPS ومقاطعها ورسالة outbox يتم في معاملة واحدة. يحمل طلب الرفع معرّف الجلسة الذي أنشأه الهاتف، والخادم يعيد سجل الجلسة الموجود عند تكرار الطلب بعد إضافة migration التوافق. ما زالت مزامنة API تعتمد HMAC القديم إلى أن تكتمل جلسات OTP/JWT؛ هذا الإصلاح لا يغيّر المصادقة الحالية. ## تصميم الجلسات وJWT - هوية الحساب هي `user_id` الداخلي؛ رقم الهاتف وسيلة دخول قابلة للتغيير. - JWT يحمل `sub` لمعرف المستخدم و`sid` للجلسة و`iat/exp` و`jti`، ولا يحمل رقم الهاتف كهوية ولا أي سر خاص. - مفاتيح توقيع JWT متغيرة خارج المستودع؛ تدويرها وخطة انتهاء الجلسات جزء من إدارة التشغيل. - Refresh token عشوائي، يحفظ hash مع تاريخ الانتهاء وآخر استعمال ومعرّف الجهاز وإمكانية الإبطال. يكشف تدوير الرمز عن إعادة استخدام رمز مسروق ويعطل عائلة الجلسة. - صلاحيات لوحة الإدارة تتحقق من الخادم في كل طلب؛ إخفاء زر في الواجهة ليس تفويضًا. - API تحت HTTPS فقط في الإنتاج. نحدد CORS للنطاقات الفعلية، ونحد معدلات OTP والدخول ورفع الصور، ونرفض حجم الصورة الزائد ونفحص نوع الملف فعليًا. - بيانات الموقع والصحة وصور الطعام خاصة افتراضيًا، لكل مستخدم، مع تصدير وحذف للحساب وفق السياسة التي تعتمد قبل الإطلاق. ## البنية المقترحة ```text تطبيق Flutter iOS/Android ─┐ موقع SportPath المتجاوب ───┼── HTTPS → نطاق واحد → PHP API → MySQL لوحة إدارة محمية ──────────┘ ├→ مزود OTP ├→ تحليل صور الطعام └→ تخزين صور محمي الهاتف: SQLite + outbox للمزامنة + إشعارات محلية CloudPanel: Nginx/PHP-FPM + MySQL + TLS + مفاتيح البيئة Git: مصدر الإصدار → SSH:2101 → نسخة إصدار → تبديل symlink ذري ``` يفضل نطاق واحد ببنية `/` للموقع و`/api/v1/` للواجهات و`/admin/` للإدارة. ملفات رفع الصور خارج `public/` ولا يمكن طلبها بعنوان مباشر؛ الوصول عبر endpoint يتحقق من ملكية المستخدم. واجهة PHP العامة تبدأ من مجلد `public`، بينما إعدادات الخادم والمكتبات والنسخ والملفات الخاصة تبقى فوق document root. المستودع الحالي يضع ملفات PHP مباشرة داخل `backend/` ولا يحوي بعد front controller أو إعداد تحميل environment مكتمل أو ملفات migration واضحة. قبل ربط CloudPanel نعيد تنظيم نقطة الدخول إلى `backend/public/`، نبني bootstrap/config من environment، ونحوّل تغييرات المخطط إلى migrations مرتبة. لا نضع `backend/schema.sql` وحده كترحيل تلقائي على قاعدة إنتاج. قاعدة البيانات في CloudPanel تنشأ باسم ومستخدم مخصصين من واجهة الإدارة، بصلاحيات قاعدة التطبيق فقط، واتصال محلي إن كانت PHP وMySQL على المضيف نفسه. تحفظ نسخة احتياطية دورية وتختبر استعادتها. اسم قاعدة البيانات والمستخدم وكلمة المرور الفعلية تأتي من بيئة الخادم؛ لا تضاف إلى Flutter أو Git. ## إعداد Git وبيئة المشروع عندما ينشئ المستخدم repository: 1. نفحص `.gitignore` قبل أول commit؛ نتأكد أن ملفات الأسرار وبيانات الهاتف وملفات قواعد البيانات والبناء غير متتبعة. 2. ينشأ `.env.example` يحتوي أسماء الإعدادات وقيمًا فارغة/وهمية فقط. ملف `.env` الحقيقي لا يُنشأ بقيم حقيقية في المستودع. 3. نضيف مفاتيح SSH للنشر في حساب Git مخصص على الخادم، بصلاحية قراءة للمستودع فقط. مفتاح SSH للدخول الإداري يبقى منفصلًا عن مفتاح سحب Git. 4. الفرع `main` يمثل إصدار الإنتاج، مع فرع staging اختياري. السكربت ينشر آخر commit منشور في الفرع المختار فقط؛ لا يدفع أو يغير branch من تلقاء نفسه. 5. إعداد CloudPanel مرة واحدة: مستخدم موقع محدود، PHP متوافق مع المشروع، مجلد إصدار ومجلد مشترك للأسرار والرفع، قاعدة ومستخدم MySQL، TLS، وdocument root يشير إلى `current/public`. أسماء إعدادات الخادم الأولية في `.env.example`، وتتوسع مع التطوير: ```dotenv APP_ENV=production APP_URL= APP_KEY= DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE= DB_USERNAME= DB_PASSWORD= JWT_SIGNING_KEY= JWT_ACCESS_TTL_SECONDS=900 OTP_PROVIDER= OTP_API_URL= OTP_API_KEY= OTP_SENDER_ID= FOOD_AI_PROVIDER= FOOD_AI_API_KEY= UPLOAD_PRIVATE_PATH= ``` تم إنشاء repository محليًا على `main` ودفع المحتوى الحالي إلى `origin/main`. إعداد النشر لا يعمل على خادم بعد؛ لا توجد حاليًا بيانات host/site user أو مجلد public صالح. القيم أعلاه أسماء توضيحية. مزود الرسائل وموفر الذكاء الاصطناعي واسم النطاق والسياسات الرقمية لم تُحسم بعد، لذا تبقى حقولها فارغة ولا يُستخدم المثال كإعداد إنتاج. ## نشر Git إلى CloudPanel والمنفذ 2101 أنشأت [deploy/sync-to-server.sh](deploy/sync-to-server.sh) ليعمل من جهاز التطوير بعد إضافة origin وتهيئة حساب الخادم. يأخذ عنوان SSH والمستخدم والجذر والنطاق من environment، ويستخدم المنفذ 2101 افتراضيًا. ينشر commit موجودًا على Git remote، ويضع كل نشر في مجلد release منفصل ثم يحول `current` إلى النسخة الجديدة بعملية تبديل واحدة. ملف البيئة ومجلدات الصور خارج نسخة Git وتبقى بين النشرات. يتطلب السكربت إعدادًا يدويًا مرة واحدة، وقيمه موضحة في [deploy/README.md](deploy/README.md). لا يتصل بالسيرفر دون تشغيل صريح. لا ينشئ قواعد بيانات، ولا يضيف أسرارًا، ولا ينفذ SQL تلقائيًا؛ إنشاء الموقع وقاعدة البيانات وTLS على CloudPanel سابق لتفعيل النشر. نراجع أول نشر على staging ونختبر health endpoint ثم نثبت document root. المتغيرات المطلوبة: `DEPLOY_HOST`, `DEPLOY_USER`, `DEPLOY_ROOT`, `DEPLOY_DOMAIN`; والاختيارية `DEPLOY_PORT` (افتراضي 2101) و`DEPLOY_REF` (افتراضي الفرع الحالي). يلزم أن يستطيع Git remote الوصول من الخادم بمفتاح deploy وأن يكون المفتاح معروفًا في `known_hosts`. يمكن `DEPLOY_DRY_RUN=1` لمراجعة القيم والتحقق المحلي دون SSH. ## إشعارات native والويب تطبيق iOS/Android يخطط تنبيهات التمرين محليًا على الجهاز، ويلغيها عند إكمال الجلسة أو تغيير وقتها أو الخطة. إعدادات المستخدم تُزامن، ثم يعيد الجهاز جدولة التنبيهات محليًا بعد استعادة التطبيق أو تغيير المنطقة الزمنية. نطلب صلاحية الإشعارات بعد شرح فائدتها، ونقدم بديلًا داخل التطبيق عند الرفض. الموقع يستطيع عرض تذكيرات داخل الصفحة. إشعارات الويب بالخلفية تعتمد على متصفح/خدمة push وصلاحية منفصلة؛ لا نعد أنها مماثلة تمامًا لتذكير محلي native أو تعمل بنفس الانتظام. الخادم لا يرسل تذكيرًا مكررًا إذا الجهاز حجز تنبيهًا محليًا. ## جدول المراحل والمعالم | المرحلة | النطاق | المعلم الذي يسمح بالانتقال | | --- | --- | --- | | 0. مستودع وقاعدة تشغيل | إنشاء Git، حماية الأسرار، توثيق البيئات، تحديد domain وOTP provider؛ إصلاح تهيئة الخدمات والحفظ والمزامنة | نسخ محلية مستقرة، commit أولي نظيف، وبنية staging قابلة للنشر | | 1. API وهوية | PHP bootstrap، migrations، MySQL، OTP provider adapter، JWT refresh sessions، تسجيل ومصادقة وصلاحيات | OTP sandbox ينجح، إعادة المحاولة آمنة، وحدود المعدل والإبطال موثقة | | 2. تجربة التمرين | أسئلة الإعداد، برنامج منشور، 30 GIF، جلسات offline، إشعارات محلية | مدرب يراجع الحركات، جلسة offline محفوظة مرة واحدة، وملخص أسبوعي صحيح | | 3. الغذاء والتقدم | تحليل صورة، تصحيح الحصة، وصفات محلية، قياسات وتقارير | اختبار وجبات موثق وتكلفة نموذجية مع تسجيل يدوي بديل | | 4. الموقع والإدارة | تجربة ويب متجاوبة، لوحة تحرير ومراجعة، التحكم بالإعدادات العامة | الأدوار مفروضة من API والعمليات الإدارية مسجلة | | 5. نشر staging/الإنتاج | CloudPanel، DB، TLS، نسخ احتياطي، health check، deploy script، مراقبة | نشر rollback قابل للتجربة، استعادة نسخة DB جرى اختبارها، واجهات HTTPS تعمل | | 6. إطلاق محدود | مستخدمون تجريبيون، مراجعة أعطال وتكلفة وتفاعل | مؤشرات أول أسابيع ضمن حدود أداء ودقة واعتماد يحددها الفريق | يمكن تطوير الموقع والإدارة بالتوازي مع واجهات API بعد ثبات عقودها. لا نضع مواعيد تقديرية قبل معرفة مزود OTP، مزود الصور، موارد الرسوم، نطاق الموقع وحالة الخادم. ## متطلبات يجب أن يوفرها صاحب المنتج في وقتها - اسم مزود OTP كما يظهر في بوابة المطور، رابط API، البلد/بلدان الإرسال، sender ID المعتمد، ووصول sandbox. لا ترسل السر داخل الدردشة أو المستودع؛ يوضع لاحقًا في بيئة الخادم. - اسم النطاق أو النطاق الفرعي وDNS الذي تملكه، وتأكيد أن منفذ SSH الخارجي 2101 هو المنفذ الصحيح لحساب Site User في CloudPanel. - بعد إعداد Git: remote URL وفرع النشر. لا حاجة إلى إرسال مفتاح خاص؛ نضبط المفتاح محليًا أو على الخادم خارج المحادثة. - إنشاء PHP site وMySQL database والمستخدم من CloudPanel أو تزويدنا بوصول إداري مصرح به عبر قناة مناسبة عند وقت التنفيذ. لا نسجل الدخول قبل وجود عنوان ومصادقة محددين. - تأكيد ما إذا كان الموقع مطلوبًا كواجهة استخدام عامة، أو لوحة إدارة مع صفحات تعريفية فقط. هذه الخطة تفترض الاثنين على مراحل. ## مراجع قرارات الهوية والجهاز - [Android: قيود المعرفات غير القابلة لإعادة الضبط](https://developer.android.com/about/versions/10/privacy/changes): Android 10 يقصر IMEI والرقم التسلسلي على صلاحيات privileged لا تملكها التطبيقات العادية المنشورة للمستخدمين. - [Android: أفضل الممارسات للمعرفات](https://developer.android.com/identity/user-data-ids): توصي المنصة بتجنب المعرفات العتادية واختيار معرّف أضيق وقابل لإعادة الضبط متى أمكن. - [Android Keystore](https://developer.android.com/privacy-and-security/keystore): يمكن حماية مفتاح خاص وربطه بمصادقة المستخدم داخل Keystore. - [Android BiometricPrompt](https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt): يوفر واجهة النظام لإجراء التحقق، وربط بعض نتائج المصادقة بعملية تشفير. - [Apple identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor): معرف المورد ليس رقمًا عتاديًا دائمًا؛ قد يتغير بعد إزالة جميع تطبيقات المورد وإعادة تثبيتها. - [Apple LocalAuthentication](https://developer.apple.com/documentation/localauthentication/logging-a-user-into-your-app-with-face-id-or-touch-id): يستخدم النظام Face ID/Touch ID لإكمال مصادقة محلية مع بديل للمستخدم. - [CloudPanel: PHP site](https://www.cloudpanel.io/docs/v2/php/applications/other/): إنشاء PHP site ومستخدمه عبر CloudPanel أو SSH، وضبط document root إلى `public` عندما تكون نقطة الدخول هناك. - [CloudPanel: أوامر قواعد البيانات وPHP site](https://www.cloudpanel.io/docs/v2/cloudpanel-cli/root-user-commands/): مرجع CLI لإدارة قاعدة البيانات والموقع والشهادة. لا يتطلب مسارنا استعمال root بعد إنشاء الموقع. هذه المراجع تسند اختيار تدفق الهوية والنشر، لكنها لا تحسم جودة OTP provider أو استضافة حساب المستخدم أو توافق إعداد CloudPanel المحدد؛ نتحقق من ذلك في بيئة staging.