Update: 2026-08-02 18:28:40

This commit is contained in:
Hamza-Ayed
2026-08-02 18:28:40 +03:00
parent 03a21aa95d
commit 02e652a786
9 changed files with 98 additions and 9 deletions
+2
View File
@@ -22,6 +22,8 @@ $food_admin_id = (string)($decoded->user_id ?? '');
try { try {
$food_con = Database::get('food'); $food_con = Database::get('food');
} catch (Exception $e) { } catch (Exception $e) {
// السبب الحقيقي في اللوج فقط — الرد يبقى عاماً.
error_log('[FOOD] DB connection failed in ' . basename(__FILE__) . ': ' . $e->getMessage());
http_response_code(503); http_response_code(503);
echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']); echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']);
exit; exit;
+2
View File
@@ -31,6 +31,8 @@ $food_passenger_id = (string)($decoded->user_id ?? '');
try { try {
$food_con = Database::get('food'); $food_con = Database::get('food');
} catch (Exception $e) { } catch (Exception $e) {
// السبب الحقيقي في اللوج فقط — الرد يبقى عاماً.
error_log('[FOOD] DB connection failed in ' . basename(__FILE__) . ': ' . $e->getMessage());
http_response_code(503); http_response_code(503);
echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']); echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']);
exit; exit;
+2
View File
@@ -28,6 +28,8 @@ $food_courier_id = (string)($decoded->user_id ?? '');
try { try {
$food_con = Database::get('food'); $food_con = Database::get('food');
} catch (Exception $e) { } catch (Exception $e) {
// السبب الحقيقي في اللوج فقط — الرد يبقى عاماً.
error_log('[FOOD] DB connection failed in ' . basename(__FILE__) . ': ' . $e->getMessage());
http_response_code(503); http_response_code(503);
echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']); echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']);
exit; exit;
+2
View File
@@ -30,6 +30,8 @@ $limiter->enforce(RateLimiter::identifier(), 'api');
try { try {
$food_con = Database::get('food'); $food_con = Database::get('food');
} catch (Exception $e) { } catch (Exception $e) {
// السبب الحقيقي في اللوج فقط — الرد يبقى عاماً.
error_log('[FOOD] DB connection failed in ' . basename(__FILE__) . ': ' . $e->getMessage());
http_response_code(503); http_response_code(503);
echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']); echo json_encode(['status' => 'failure', 'message' => 'Food service unavailable']);
exit; exit;
+5
View File
@@ -5,6 +5,7 @@ CREATE DATABASE IF NOT EXISTS `intaleqDB1` CHARACTER SET utf8mb4 COLLATE utf8mb4
CREATE DATABASE IF NOT EXISTS `locationDB` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS `locationDB` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `intaleq-ridesDB` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS `intaleq-ridesDB` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `siroTransitDb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS `siroTransitDb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS `siro_food` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- إنشاء المستخدمين وإعطائهم الصلاحيات لكي تتطابق البيئة مع اللايف 100% -- إنشاء المستخدمين وإعطائهم الصلاحيات لكي تتطابق البيئة مع اللايف 100%
CREATE USER IF NOT EXISTS 'intaleqapp-jordan-siro'@'%' IDENTIFIED BY '7B7skfibrexQrGCUG1Y8'; CREATE USER IF NOT EXISTS 'intaleqapp-jordan-siro'@'%' IDENTIFIED BY '7B7skfibrexQrGCUG1Y8';
@@ -22,4 +23,8 @@ GRANT ALL PRIVILEGES ON `intaleq-ridesDB`.* TO 'intaleq-rides'@'%';
CREATE USER IF NOT EXISTS 'siroTransitUser'@'%' IDENTIFIED BY 'eZiDYxCJ2yyGfC32zG0Z'; CREATE USER IF NOT EXISTS 'siroTransitUser'@'%' IDENTIFIED BY 'eZiDYxCJ2yyGfC32zG0Z';
GRANT ALL PRIVILEGES ON `siroTransitDb`.* TO 'siroTransitUser'@'%'; GRANT ALL PRIVILEGES ON `siroTransitDb`.* TO 'siroTransitUser'@'%';
-- ⚠️ كلمة المرور هنا يجب أن تطابق DB_FOOD_PASS في ملف .env وإلا فشل الاتصال.
CREATE USER IF NOT EXISTS 'siro_food'@'%' IDENTIFIED BY 'CHANGE_ME_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON `siro_food`.* TO 'siro_food'@'%';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
+1
View File
@@ -10,6 +10,7 @@ class BoxName {
static const String gender = "gender"; static const String gender = "gender";
static const String themeMode = "themeMode"; static const String themeMode = "themeMode";
static const String jwt = "jwt"; static const String jwt = "jwt";
static const String walletJwt = "walletJwt";
static const String lowEndMode = "lowEndMode"; static const String lowEndMode = "lowEndMode";
static const String deviceFpEncrypted = "deviceFpEncrypted"; static const String deviceFpEncrypted = "deviceFpEncrypted";
static const String appVersionChecked = "appVersionChecked"; static const String appVersionChecked = "appVersionChecked";
@@ -284,9 +284,64 @@ class LoginController extends GetxController {
// • يرجع hmac مع الـ jwt ويخزنه في GetStorage // • يرجع hmac مع الـ jwt ويخزنه في GetStorage
// • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header) // • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header)
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
// • يُخزَّن التوكن ويُعاد استخدامه ما دام أمامه أكثر من 10 ثوانٍ
// (عمره 300ث)، فلا نُسجّل دخولاً مع كل طلب محفظة.
static Future<String?>? _walletJwtFuture;
Future<String?> getJwtWallet() async { Future<String?> getJwtWallet() async {
// طلب قيد التنفيذ؟ شارك نتيجته بدل إطلاق طلب ثانٍ.
if (_walletJwtFuture != null) {
Log.print('⏳ getJwtWallet: طلب قيد التنفيذ — إعادة استخدام نفس الـ future.');
return _walletJwtFuture!;
}
_walletJwtFuture = _getJwtWalletInternal();
try {
return await _walletJwtFuture!;
} finally {
_walletJwtFuture = null;
}
}
/// هل التوكن المخزَّن صالح ويخصّ الراكب الحالي؟
/// نتحقق من `user_id` أيضاً كي لا يُعاد استخدام توكن حساب سابق
/// على نفس الجهاز بعد تبديل المستخدم.
bool _isWalletJwtUsable(String jwt, String passengerId) {
try {
final parts = jwt.split('.');
if (parts.length != 3) return false;
String payload = parts[1];
while (payload.length % 4 != 0) {
payload += '=';
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
if ((decoded['user_id'] ?? '').toString() != passengerId) return false;
final exp = decoded['exp'];
if (exp == null) return false;
// هامش 10 ثوانٍ حتى لا ينتهي التوكن أثناء الطلب نفسه
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 10000);
} catch (_) {
return false;
}
}
Future<String?> _getJwtWalletInternal() async {
dev = Platform.isAndroid ? 'android' : 'ios'; dev = Platform.isAndroid ? 'android' : 'ios';
final String passengerId = box.read(BoxName.passengerID)?.toString() ?? '';
// ── إعادة استخدام التوكن المخزَّن إن كان صالحاً ────────────────
final String cachedJwt = box.read(BoxName.walletJwt)?.toString() ?? '';
final String cachedHmac = box.read(BoxName.hmac)?.toString() ?? '';
if (cachedJwt.isNotEmpty &&
cachedHmac.isNotEmpty &&
passengerId.isNotEmpty &&
_isWalletJwtUsable(cachedJwt, passengerId)) {
Log.print('🔑 توكن محفظة صالح في التخزين — تخطّي طلب السيرفر.');
return cachedJwt;
}
// نعيد حساب البصمة أولاً كي لا نرسل قيمة GCM قديمة عالقة في التخزين // نعيد حساب البصمة أولاً كي لا نرسل قيمة GCM قديمة عالقة في التخزين
// من نسخة سابقة من التطبيق (مثل getJWT تماماً). // من نسخة سابقة من التطبيق (مثل getJWT تماماً).
await DeviceHelper.getDeviceFingerprint(); await DeviceHelper.getDeviceFingerprint();
@@ -323,6 +378,11 @@ class LoginController extends GetxController {
box.write(BoxName.hmac, hmac); box.write(BoxName.hmac, hmac);
} }
// نخزّن التوكن ليُعاد استخدامه حتى قرب انتهائه بدل طلب جديد كل مرة
if (jwt != null) {
box.write(BoxName.walletJwt, jwt);
}
return jwt; return jwt;
} }
+18 -3
View File
@@ -284,6 +284,16 @@ class CRUD {
// التغيير: إضافة X-Device-FP header // التغيير: إضافة X-Device-FP header
// 3 headers معاً: JWT + HMAC + FP // 3 headers معاً: JWT + HMAC + FP
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
/// توكن المحفظة صار يُخزَّن ويُعاد استخدامه. لو رفضه السيرفر (توكن
/// مُلغى أو تغيّر سرّ التوقيع) نمسح النسخة المخزّنة كي يُطلب توكن
/// جديد في المحاولة التالية بدل أن نعلق على توكن ميت.
void _invalidateWalletJwtIfRejected(dynamic result) {
if (result == 'token_expired') {
Log.print('🗑️ توكن المحفظة مرفوض — مسح النسخة المخزّنة.');
box.remove(BoxName.walletJwt);
}
}
Future<dynamic> postWallet({ Future<dynamic> postWallet({
required String link, required String link,
Map<String, dynamic>? payload, Map<String, dynamic>? payload,
@@ -297,11 +307,13 @@ class CRUD {
'X-HMAC-Auth': hmac.toString(), 'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), 'X-Device-FP': _getFpHeader(),
}; };
Log.print('headers: $headers');
Log.print('payload: $payload'); Log.print('payload: $payload');
Log.print('link: $link'); Log.print('link: $link');
return await _makeRequest(link: link, payload: payload, headers: headers); final result =
await _makeRequest(link: link, payload: payload, headers: headers);
_invalidateWalletJwtIfRejected(result);
return result;
} }
Future<dynamic> getWallet({ Future<dynamic> getWallet({
@@ -318,7 +330,10 @@ class CRUD {
'X-Device-FP': _getFpHeader(), 'X-Device-FP': _getFpHeader(),
}; };
return await _makeRequest(link: link, payload: payload, headers: headers); final result =
await _makeRequest(link: link, payload: payload, headers: headers);
_invalidateWalletJwtIfRejected(result);
return result;
} }
// ======================================================================= // =======================================================================
+6 -6
View File
@@ -1297,10 +1297,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.19" version: "0.12.18"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -1313,10 +1313,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.17.0"
mime: mime:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1901,10 +1901,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.11" version: "0.7.9"
timezone: timezone:
dependency: transitive dependency: transitive
description: description: