Compare commits

2 Commits
Author SHA1 Message Date
Hamza-Ayed c35b350b31 Update: 2026-07-23 16:56:57 2026-07-23 16:56:58 +03:00
Hamza-Ayed 1cef598fc5 Update: 2026-07-23 02:19:01 2026-07-23 02:19:01 +03:00
10 changed files with 121 additions and 50 deletions
+1 -1
View File
@@ -552,7 +552,7 @@ Therefore, do NOT assume a specific field is on the front or the back of a card.
/* ================== 11) Notification ================== */ /* ================== 11) Notification ================== */
try { try {
$fcmSendUrl = 'https://api.intaleq.xyz/siro/ride/firebase/send_fcm.php'; $fcmSendUrl = getenv('FCM_ENDPOINT_URL') ?: 'http://nginx/backend/ride/firebase/send_fcm.php';
$driverFullName = $raw_first_name . ' ' . $raw_last_name; $driverFullName = $raw_first_name . ' ' . $raw_last_name;
$notificationTitle = 'تسجيل سائق جديد'; $notificationTitle = 'تسجيل سائق جديد';
+8 -31
View File
@@ -62,36 +62,13 @@ echo "Silent Push triggered for $sentCount inactive users.\n";
* Helper to send Silent FCM * Helper to send Silent FCM
*/ */
function sendSilentFcmNotification($token, $data) { function sendSilentFcmNotification($token, $data) {
// Basic curl request to FCM API for silent push if (function_exists('sendFCM_Internal')) {
$url = 'https://fcm.googleapis.com/fcm/send'; return sendFCM_Internal($token, '', '', $data, 'SilentPush', false, 'silent');
}
// Replace with your actual server key if (!class_exists('FcmService')) {
$serverKey = getenv('FCM_SERVER_KEY') ?: 'YOUR_LEGACY_SERVER_KEY'; require_once __DIR__ . '/../core/Services/FcmService.php';
}
$fields = [ $fcm = new FcmService();
'to' => $token, return $fcm->send($token, '', '', $data, 'SilentPush', 'silent');
'data' => $data,
'content_available' => true, // Required for iOS to wake up in background
'priority' => 'high' // Sometimes required to wake up Android
];
$headers = [
'Authorization: key=' . $serverKey,
'Content-Type: application/json'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
$result = curl_exec($ch);
curl_close($ch);
return $result;
} }
?> ?>
+9 -3
View File
@@ -11,9 +11,15 @@ class FcmService
public function __construct(?Redis $redis = null) public function __construct(?Redis $redis = null)
{ {
$this->redis = $redis; $this->redis = $redis;
// المسار بناء على بنية المشروع $envPath = getenv('FIREBASE_SERVICE_ACCOUNT_PATH') ?: '';
$this->serviceAccountFile = getenv('FIREBASE_SERVICE_ACCOUNT_PATH'); if (!empty($envPath) && file_exists($envPath)) {
$this->serviceAccountFile = $envPath;
} elseif (file_exists('/keys/firebase_service_account.json')) {
$this->serviceAccountFile = '/keys/firebase_service_account.json';
} else {
$this->serviceAccountFile = __DIR__ . '/../../keys/firebase_service_account.json';
}
} }
// ── إرسال إشعار ──────────────────────────────────────── // ── إرسال إشعار ────────────────────────────────────────
+16 -4
View File
@@ -12,10 +12,22 @@ function base64UrlEncode($data) {
// ============================================================================ // ============================================================================
// دالة الحصول على Access Token من Google OAuth2 // دالة الحصول على Access Token من Google OAuth2
// ============================================================================ // ============================================================================
function getFCMAccessToken($serviceAccountPath = null) { function resolveFCMServiceAccountPath($serviceAccountPath = null) {
if (!$serviceAccountPath) { if (!empty($serviceAccountPath) && file_exists($serviceAccountPath)) {
$serviceAccountPath = __DIR__ . '/service-account.json'; return $serviceAccountPath;
} }
$envPath = getenv('FIREBASE_SERVICE_ACCOUNT_PATH') ?: '';
if (!empty($envPath) && file_exists($envPath)) {
return $envPath;
}
if (file_exists('/keys/firebase_service_account.json')) {
return '/keys/firebase_service_account.json';
}
return __DIR__ . '/service-account.json';
}
function getFCMAccessToken($serviceAccountPath = null) {
$serviceAccountPath = resolveFCMServiceAccountPath($serviceAccountPath);
if (!file_exists($serviceAccountPath)) { if (!file_exists($serviceAccountPath)) {
error_log("❌ FCM: service-account.json not found at: $serviceAccountPath"); error_log("❌ FCM: service-account.json not found at: $serviceAccountPath");
@@ -73,7 +85,7 @@ function sendFCMNotification($params) {
$data = $params['data'] ?? []; $data = $params['data'] ?? [];
$tone = $params['tone'] ?? 'default'; $tone = $params['tone'] ?? 'default';
$isTopic = $params['isTopic'] ?? false; $isTopic = $params['isTopic'] ?? false;
$serviceAccountPath = $params['service_account_path'] ?? __DIR__ . '/service-account.json'; $serviceAccountPath = resolveFCMServiceAccountPath($params['service_account_path'] ?? null);
// التحقق من البيانات الأساسية // التحقق من البيانات الأساسية
if (empty($token) || empty($title) || empty($body)) { if (empty($token) || empty($title) || empty($body)) {
+14 -3
View File
@@ -2,16 +2,27 @@
// send_fcm.php - FCM HTTP v1 Sender (Internal use only) // send_fcm.php - FCM HTTP v1 Sender (Internal use only)
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
// 🔐 Require internal API key for authentication // 🔐 Require internal API key for authentication if set
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? ''; $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$expectedKey = getenv('FCM_INTERNAL_API_KEY'); $expectedKey = getenv('FCM_INTERNAL_API_KEY');
if (empty($expectedKey) || !hash_equals($expectedKey, $apiKey)) { if (!empty($expectedKey) && !hash_equals($expectedKey, $apiKey)) {
http_response_code(403); http_response_code(403);
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']); echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
exit; exit;
} }
$serviceAccountFile = __DIR__ . '/service-account.json'; function resolveServiceAccountPath(): string {
$envPath = getenv('FIREBASE_SERVICE_ACCOUNT_PATH') ?: '';
if (!empty($envPath) && file_exists($envPath)) {
return $envPath;
}
if (file_exists('/keys/firebase_service_account.json')) {
return '/keys/firebase_service_account.json';
}
return __DIR__ . '/service-account.json';
}
$serviceAccountFile = resolveServiceAccountPath();
// السماح فقط بـ POST // السماح فقط بـ POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
@@ -21,8 +21,8 @@ try {
FROM ride FROM ride
WHERE passenger_id = ? WHERE passenger_id = ?
AND ( AND (
status IN ('Apply', 'Applied', 'accepted', 'arrived', 'Arrived', 'Begin') AND created_at >= NOW() - INTERVAL 24 HOUR status IN ('Apply', 'Applied', 'accepted', 'arrived', 'Arrived', 'Begin') AND created_at >= NOW() - INTERVAL 1 HOUR
OR (status = 'Finished' AND created_at >= NOW() - INTERVAL 24 HOUR) OR (status = 'Finished' AND created_at >= NOW() - INTERVAL 1 HOUR)
) )
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 1 LIMIT 1
@@ -39,7 +39,72 @@
--- ---
## 2. 📐 التوسع الأفقي وفصل السيرفرات (Horizontal Scaling & Architecture Decoupling) ## 2. 📐 المعمارية الرسمية للنظام (System Architecture & Mermaid Diagram)
يتكون نظام سيرو الحالي من بنية متكاملة عالية الأداء تعيش على سيرفر فيزيائي موحد، ومقسمة إلى حاويات دكر متخصصة مترابطة عبر شبكة دكر الداخلية:
```mermaid
graph TD
subgraph Clients["📱 تطبيقات الهواتف المحمولة (Clients)"]
RiderApp["تطبيق الراكب (Rider App)"]
DriverApp["تطبيق السائق (Driver App)"]
end
subgraph Server["🖥️ السيرفر الموحد (Docker Compose Infrastructure - siro)"]
subgraph NginxEntry["🌐 بوابة Nginx المركزية (Port 80 / 8080)"]
Nginx["Nginx Reverse Proxy"]
end
subgraph FPMContainer["⚡ حاوية المعالجة الرئيسية (PHP-FPM: php:9000)"]
BackendAPI["الباك إند الرئيسي (/var/www/backend)"]
PaymentAPI["سيرفر المدفوعات (/var/www/v2)"]
LocationAPI["سيرفر المواقع (/var/www/loction_server)"]
end
subgraph SocketDriver["🔌 حاوية سوكيت السائقين (socket_driver)"]
DriverWS["Workerman WS Server (Port 2020)"]
DriverInternalHTTP["مستقبل الإشعارات الداخلي (Port 2021)"]
end
subgraph SocketPassenger["🔌 حاوية سوكيت الركاب (socket_passenger)"]
PassengerWS["Workerman WS Server (Port 3030)"]
PassengerInternalHTTP["مستقبل الإشعارات الداخلي (Port 3031)"]
end
subgraph DataStorage["💾 قواعد البيانات والكاش المحلي"]
MySQL[("MySQL 8.4 Database")]
Redis[("Redis 7.2 GEO & Pub/Sub")]
end
end
%% اتصالات العملاء
RiderApp -- "HTTPS REST API" --> Nginx
DriverApp -- "HTTPS REST API" --> Nginx
DriverApp -- "WebSocket (Port 2020)" --> DriverWS
RiderApp -- "WebSocket (Port 3030)" --> PassengerWS
%% توجيه Nginx الداخلي (FastCGI)
Nginx -- "FastCGI /backend/..." --> BackendAPI
Nginx -- "FastCGI /v2/main/..." --> PaymentAPI
Nginx -- "FastCGI /loction_server/..." --> LocationAPI
%% الاتصالات الداخلية السريعة (Server-to-Server)
BackendAPI -- "cURL (http://nginx/v2/main/...)" --> PaymentAPI
BackendAPI -- "cURL (http://nginx/loction_server/driver_socket.php)" --> DriverInternalHTTP
LocationAPI -- "cURL (http://nginx/loction_server/passenger_socket.php)" --> PassengerInternalHTTP
%% قواعد البيانات والـ Redis
BackendAPI --> MySQL
PaymentAPI --> MySQL
BackendAPI --> Redis
LocationAPI --> Redis
DriverWS --> Redis
PassengerWS --> Redis
```
---
### أ. التوسع الأفقي وفصل السيرفرات عند الحاجة (Horizontal Scaling)
عند الحاجة للتوسع الضخم (أكثر من 50,000 رحلة في الساعة)، تم تصميم نظام سيرو ليدعم الفصل الفوري إلى **سيرفرين أو أكثر** عبر حاويات الدوكر المستقلة: عند الحاجة للتوسع الضخم (أكثر من 50,000 رحلة في الساعة)، تم تصميم نظام سيرو ليدعم الفصل الفوري إلى **سيرفرين أو أكثر** عبر حاويات الدوكر المستقلة:
+1 -1
View File
@@ -272,7 +272,7 @@ function sendFCM_Async(string $token, string $title, string $body, array $rideDa
$http = new AsyncHttp(); $http = new AsyncHttp();
$http->request( $http->request(
'https://api.intaleq.xyz/intaleq/ride/firebase/send_fcm.php', getenv('FCM_ENDPOINT_URL') ?: 'http://nginx/backend/ride/firebase/send_fcm.php',
[ [
'method' => 'POST', 'method' => 'POST',
'data' => json_encode([ 'data' => json_encode([
+2 -2
View File
@@ -63,9 +63,9 @@ class AppLink {
case 'Egypt': case 'Egypt':
return 'https://location-egypt.siromove.com'; return 'https://location-egypt.siromove.com';
case 'Jordan': case 'Jordan':
return 'https://jordan-siro.intaleqapp.com'; return 'https://jordan-siro.intaleqapp.com:2020';
default: default:
return 'https://location-jordan.siromove.com'; return 'https://jordan-siro.intaleqapp.com:2020';
} }
} }
+2 -2
View File
@@ -152,9 +152,9 @@ class AppLink {
case 'Egypt': case 'Egypt':
return 'https://api-egypt.siromove.com'; return 'https://api-egypt.siromove.com';
case 'Jordan': case 'Jordan':
return 'https://jordan-siro.intaleqapp.com'; return 'https://jordan-siro.intaleqapp.com:3030';
default: default:
return 'https://api.siromove.com'; return 'https://jordan-siro.intaleqapp.com:3030';
} }
} }