table('ride') ->where('id', $rideId) ->whereIn('status', ['Applied', 'Arrived', 'Begin']) ->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not active'], 404); } $location = DB::connection('tracking')->table('car_locations') ->where('driver_id', $ride->driver_id) ->first(); if (!$location) { return response()->json(['status' => 'failure', 'message' => 'Driver location not available'], 404); } return response()->json([ 'status' => 'success', 'data' => [ 'latitude' => $location->latitude, 'longitude' => $location->longitude, 'heading' => $location->heading, 'speed' => $location->speed, 'updated_at' => $location->updated_at, ], ]); } /** * GET /v2/tracking/public/{rideId}?hash=XXX * Public tracking link for parents/friends — uses HMAC hash instead of auth */ public function publicTrack(Request $request, int $rideId): JsonResponse { $hash = $request->input('hash'); if (!$hash) { return response()->json(['status' => 'failure', 'message' => 'Missing hash'], 400); } // Verify hash: HMAC-SHA256(ride_id, secret_salt) $expectedHash = hash_hmac('sha256', (string) $rideId, config('intaleq.secret_salt_parent')); if (!hash_equals($expectedHash, $hash)) { return response()->json(['status' => 'failure', 'message' => 'Invalid hash'], 403); } $ride = DB::connection('ride')->table('ride') ->where('id', $rideId) ->whereIn('status', ['Applied', 'Arrived', 'Begin']) ->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not active'], 404); } $location = DB::connection('tracking')->table('car_locations') ->where('driver_id', $ride->driver_id) ->first(); return response()->json([ 'status' => 'success', 'data' => [ 'latitude' => $location->latitude ?? null, 'longitude' => $location->longitude ?? null, 'heading' => $location->heading ?? null, 'ride_status' => $ride->status, ], ]); } /** * GET /v2/tracking/heatmap * المطورة لتطابق V1 مع تحسين الأداء */ public function heatmap(Request $request): JsonResponse { $precision = 2; // دقة الشبكة (~1 كم) $grid = []; // 1. جلب طلبات الانتظار (Waiting) من قاعدة البيانات الأساسية $waitingRides = DB::connection('primary')->table('waitingRides') ->select('start_lat', 'start_lng') ->whereIn('status', ['wait', 'waiting']) ->get(); foreach ($waitingRides as $ride) { $this->addToGrid($grid, $ride->start_lat, $ride->start_lng, $precision, 5); } // 2. جلب الطلبات الضائعة (Timeout/Cancelled) من خادم الرحلات - آخر 20 دقيقة $missedRides = DB::connection('ride')->table('ride') ->select('start_location') ->whereIn('status', ['timeout', 'cancelled_no_driver_found']) ->where('created_at', '>=', now()->subMinutes(20)) ->get(); foreach ($missedRides as $ride) { $parts = explode(',', $ride->start_location); if (count($parts) == 2) { $this->addToGrid($grid, $parts[0], $parts[1], $precision, 8); } } // 3. جلب الطلبات النشطة (Active) - آخر 15 دقيقة $activeRides = DB::connection('ride')->table('ride') ->select('start_location') ->where('created_at', '>=', now()->subMinutes(15)) ->whereNotIn('status', ['timeout', 'cancelled_no_driver_found']) ->get(); foreach ($activeRides as $ride) { $parts = explode(',', $ride->start_location); if (count($parts) == 2) { $this->addToGrid($grid, $parts[0], $parts[1], $precision, 1); } } // 4. معالجة البيانات النهائية (التصنيف والـ Surge) $finalData = []; foreach ($grid as $cell) { $score = $cell['score']; $count = $cell['count']; $intensity = 'normal'; $surge = 1.0; if ($score >= 15 || $count >= 5) { $intensity = 'high'; $surge = 1.5; } elseif ($score >= 8 || $count >= 3) { $intensity = 'medium'; $surge = 1.2; } $finalData[] = [ 'lat' => $cell['lat'], 'lng' => $cell['lng'], 'count' => $count, 'intensity' => $intensity, 'surge' => $surge ]; } return response()->json([ 'status' => 'success', 'data' => $finalData ]); } /** دالة مساعدة لتجميع النقاط في الشبكة */ private function addToGrid(&$grid, $lat, $lng, $precision, $weight): void { if (empty($lat) || empty($lng)) return; $rLat = round((float)$lat, $precision); $rLng = round((float)$lng, $precision); $key = "{$rLat},{$rLng}"; if (!isset($grid[$key])) { $grid[$key] = ['lat' => $rLat, 'lng' => $rLng, 'count' => 0, 'score' => 0]; } $grid[$key]['count']++; $grid[$key]['score'] += $weight; } /** GET /v2/tracking/captain-stats */ public function captainStats(Request $request): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $totalRides = DB::connection('ride')->table('ride') ->where('driver_id', $driverId) ->where('status', 'finish') ->count(); $todayRides = DB::connection('ride')->table('ride') ->where('driver_id', $driverId) ->where('status', 'finish') ->whereDate('rideTimeFinish', today()) ->count(); $todayEarnings = DB::connection('ride')->table('ride') ->where('driver_id', $driverId) ->where('status', 'finish') ->whereDate('rideTimeFinish', today()) ->sum('price_for_driver'); $workHours = DB::connection('tracking')->table('driver_daily_summary') ->where('driver_id', $driverId) ->where('date', today()->toDateString()) ->value('total_seconds') ?? 0; return response()->json([ 'status' => 'success', 'data' => [ 'total_rides' => $totalRides, 'today_rides' => $todayRides, 'today_earnings' => round($todayEarnings, 2), 'today_work_seconds' => $workHours, ], ]); } }