encryption = $encryption; $this->fcm = $fcm; $this->socket = $socket; } public function store(Request $request): JsonResponse { // 1. Validation (Adding new required fields for the 33-item array) $request->validate([ 'start_location' => 'required|string', 'end_location' => 'required|string', 'price' => 'required|numeric|min:0', 'car_type' => 'required|string', 'distance' => 'required|numeric', 'price_for_driver' => 'nullable|numeric|min:0', 'price_for_passenger' => 'nullable|numeric|min:0', 'status' => 'nullable|string', // Socket / Legacy array specific fields 'passenger_name' => 'nullable|string', 'passenger_phone' => 'nullable|string', 'passenger_token' => 'nullable|string', 'passenger_email' => 'nullable|string', 'passenger_wallet' => 'nullable|string', 'passenger_rating' => 'nullable|string', 'start_name' => 'nullable|string', 'end_name' => 'nullable|string', 'duration_text' => 'nullable|string', 'distance_text' => 'nullable|string', 'is_wallet' => 'nullable|string', 'has_steps' => 'nullable|string', 'step0' => 'nullable|string', 'step1' => 'nullable|string', 'step2' => 'nullable|string', 'step3' => 'nullable|string', 'step4' => 'nullable|string', ]); $passengerId = $request->attributes->get('_jwt_user_id'); $activeRide = DB::connection('ride')->table('ride') ->where('passenger_id', $passengerId) ->whereIn('status', ['waiting', 'going_to_passenger', 'arrived', 'started', 'Begin']) ->first(); if ($activeRide) { return response()->json([ 'status' => 'failure', 'message' => 'You already have an active ride', ], 409); } $rideData = [ 'start_location' => $request->input('start_location'), 'end_location' => $request->input('end_location'), 'date' => now()->toDateString(), 'time' => now()->toTimeString(), 'endtime' => '00:00:00', 'price' => $request->input('price'), 'passenger_id' => $passengerId, 'driver_id' => '0', 'status' => $request->input('status', 'waiting'), 'carType' => $request->input('car_type', 'Speed'), 'price_for_driver' => $request->input('price_for_driver', $request->input('price')), 'price_for_passenger' => $request->input('price_for_passenger', $request->input('price')), 'distance' => $request->input('distance'), ]; DB::connection('primary')->beginTransaction(); DB::connection('ride')->beginTransaction(); try { $insertedId = DB::connection('primary')->table('ride')->insertGetId($rideData); $rideData['id'] = $insertedId; DB::connection('ride')->table('ride')->insert($rideData); DB::connection('primary')->commit(); DB::connection('ride')->commit(); // Prepare Legacy Array [0..33] $partsStart = explode(',', $request->input('start_location')); $startLat = trim($partsStart[0] ?? ""); $startLng = trim($partsStart[1] ?? ""); $partsEnd = explode(',', $request->input('end_location')); $endLat = trim($partsEnd[0] ?? ""); $endLng = trim($partsEnd[1] ?? ""); $price = (float) $request->input('price'); $priceForDriver = (float) $request->input('price_for_driver', $price); $kazan = $price - $priceForDriver; $payloadTemplate = []; $payloadTemplate[0] = (string)$startLat; $payloadTemplate[1] = (string)$startLng; $payloadTemplate[2] = (string)number_format($price, 2, '.', ''); $payloadTemplate[3] = (string)$endLat; $payloadTemplate[4] = (string)$endLng; $payloadTemplate[5] = (string)$request->input('distance_text', ''); $payloadTemplate[6] = ""; $payloadTemplate[7] = (string)$passengerId; $payloadTemplate[8] = (string)$request->input('passenger_name', ''); $payloadTemplate[9] = (string)$request->input('passenger_token', ''); $payloadTemplate[10] = (string)$request->input('passenger_phone', ''); $payloadTemplate[11] = (string)$request->input('distance', '0'); $payloadTemplate[12] = "1"; $payloadTemplate[13] = (string)$request->input('is_wallet', '0'); $payloadTemplate[14] = (string)$request->input('distance', '0'); $payloadTemplate[15] = (string)$request->input('duration_text', ''); $payloadTemplate[16] = (string)$insertedId; $payloadTemplate[17] = ""; $payloadTemplate[18] = ""; $payloadTemplate[19] = (string)$request->input('duration_text', ''); $payloadTemplate[20] = $request->input('has_steps') ?: 'false'; $payloadTemplate[21] = (string)$request->input('step0', ''); $payloadTemplate[22] = (string)$request->input('step1', ''); $payloadTemplate[23] = (string)$request->input('step2', ''); $payloadTemplate[24] = (string)$request->input('step3', ''); $payloadTemplate[25] = (string)$request->input('step4', ''); $payloadTemplate[26] = (string)number_format($priceForDriver, 2, '.', ''); $payloadTemplate[27] = (string)$request->input('passenger_wallet', '0'); $payloadTemplate[28] = (string)$request->input('passenger_email', ''); $payloadTemplate[29] = (string)$request->input('start_name', ''); $payloadTemplate[30] = (string)$request->input('end_name', ''); $payloadTemplate[31] = (string)$request->input('car_type', 'Speed'); $payloadTemplate[32] = (string)number_format($kazan, 2, '.', ''); $payloadTemplate[33] = (string)$request->input('passenger_rating', '5.0'); ksort($payloadTemplate); $payloadArray = array_values($payloadTemplate); // Send to Market exactly like V1 $this->socket->sendToLocationServer('market_new_ride', [ 'payload' => $payloadArray ]); return response()->json([ 'status' => 'success', 'message' => $insertedId, ], 200); } catch (\Exception $e) { DB::connection('primary')->rollBack(); DB::connection('ride')->rollBack(); \Log::error('AddRide Critical Error: ' . $e->getMessage()); return response()->json([ 'status' => 'failure', 'message' => 'Failed to add ride', ], 500); } } public function accept(Request $request, int $rideId): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $userType = $request->attributes->get('_jwt_user_type'); $status = $request->input('status', 'Apply'); // Allow dynamic status but default to Apply DB::connection('ride')->beginTransaction(); try { $ride = Ride::lockForUpdate()->find($rideId); if (!$ride || !in_array($ride->status, ['waiting', 'wait'])) { DB::connection('ride')->rollBack(); return response()->json([ 'status' => 'failure', 'message' => 'Ride not available (Already taken)', ], 409); // Keep 409 for conflict but failure text from V1 } // Remote DB Update $ride->update([ 'driver_id' => $driverId, 'status' => $status, 'rideTimeStart' => now(), // V1 acceptRide does this 'DriverIsGoingToPassenger' => now(), ]); // Local DB Update DB::connection('primary') ->table('ride') ->where('id', $rideId) ->update([ 'driver_id' => $driverId, 'status' => $status, 'rideTimeStart' => now(), ]); // Remove from waiting rides (legacy) DB::connection('primary') ->table('waitingRides') ->where('id', (string) $rideId) ->update(['status' => $status]); // Driver Orders $checkOrder = DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->first(); if ($checkOrder) { DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId) ->update(['driver_id' => $driverId, 'status' => $status, 'created_at' => now()]); } else { DB::connection('primary')->table('driver_orders') ->insert(['driver_id' => $driverId, 'order_id' => (string)$rideId, 'status' => $status, 'created_at' => now()]); } DB::connection('ride')->commit(); // Fetch Driver Info for Passenger $driverRaw = DB::connection('primary') ->table('driver as d') ->leftJoin('CarRegistration as c', 'c.driverID', '=', 'd.id') ->leftJoin('driverToken as dt', 'dt.captain_id', '=', 'd.id') ->leftJoin('ratingDriver as r', 'r.driver_id', '=', 'd.id') ->select( 'd.id as driver_id', 'd.first_name', 'd.last_name', 'd.gender', 'd.phone', 'c.make', 'c.model', 'c.car_plate', 'c.year', 'c.color', 'c.color_hex', 'dt.token', DB::raw('ROUND(AVG(r.rating), 2) as ratingDriver') ) ->where('d.id', $driverId) ->groupBy('d.id', 'd.first_name', 'd.last_name', 'd.gender', 'd.phone', 'c.make', 'c.model', 'c.car_plate', 'c.year', 'c.color', 'c.color_hex', 'dt.token') ->first(); $driverInfo = []; if ($driverRaw) { $fieldsToDecrypt = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token']; foreach ((array)$driverRaw as $key => $value) { if (in_array($key, $fieldsToDecrypt) && !empty($value)) { $driverInfo[$key] = $this->encryption->decrypt($value); } else { $driverInfo[$key] = $value; } } $driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? '')); if (empty($driverInfo['ratingDriver'])) { $driverInfo['ratingDriver'] = "5.0"; } } // Notify passenger via socket $this->socket->notifyPassenger($ride->passenger_id, [ 'ride_id' => $rideId, 'status' => 'accepted', // App uses 'accepted' 'driver_id' => $driverId, 'driver_info' => $driverInfo ]); // Notify passenger via FCM $passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first(); if ($passengerToken) { $decryptedToken = $this->encryption->decrypt($passengerToken->token); $this->fcm->sendToDevice( $decryptedToken, "Ride Accepted 🚖", "Captain " . ($driverInfo['driverName'] ?? 'Driver') . " is coming to you.", ['ride_id' => (string) $rideId, 'driver_info' => $driverInfo], 'Accepted Ride' ); } // Cleanup Marketplace $this->socket->sendToLocationServer('ride_taken_event', [ 'ride_id' => $rideId, 'taken_by_driver_id' => $driverId ]); return response()->json([ 'status' => 'success', 'message' => $driverInfo ]); } catch (\Exception $e) { DB::connection('ride')->rollBack(); return response()->json(['status' => 'failure', 'message' => 'Error: ' . $e->getMessage()], 500); } } public function arrive(Request $request, int $rideId): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $ride = Ride::where('id', $rideId)->where('driver_id', $driverId)->whereIn('status', ['Apply', 'Applied'])->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404); } $ride->update(['status' => 'arrived']); // Matching V1 lowercase DB::connection('primary')->table('ride')->where('id', $rideId)->update(['status' => 'arrived', 'updated_at' => now()]); // Socket $this->socket->notifyPassenger($ride->passenger_id, [ 'status' => 'arrived', 'ride_id' => $rideId, 'msg' => 'السائق وصل إلى موقعك 🚖' ]); // FCM $passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first(); if ($passengerToken) { $this->fcm->sendToDevice( $this->encryption->decrypt($passengerToken->token), "السائق وصل 📍", "الكابتن ينتظرك في الموقع المحدد.", ['ride_id' => (string) $rideId], 'Arrive Ride' ); } return response()->json(['status' => 'success', 'message' => 'Arrival notified successfully']); } public function start(Request $request, int $rideId): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $status = 'Begin'; $ride = Ride::where('id', $rideId)->where('driver_id', $driverId)->whereIn('status', ['Apply', 'Applied', 'arrived', 'Arrived'])->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found or not ready'], 404); } $ride->update(['status' => $status, 'rideTimeStart' => now()]); DB::connection('primary')->table('ride')->where('id', $rideId)->update(['status' => $status, 'rideTimeStart' => now()]); // Driver Orders $checkOrder = DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->first(); if ($checkOrder) { DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->update(['driver_id' => $driverId, 'status' => $status, 'created_at' => now()]); } else { DB::connection('primary')->table('driver_orders')->insert(['driver_id' => $driverId, 'order_id' => (string)$rideId, 'status' => $status, 'created_at' => now()]); } // Socket $this->socket->notifyPassenger($ride->passenger_id, [ 'ride_id' => $rideId, 'status' => 'started', 'msg' => 'بدأت الرحلة، نتمنى لك سلامة الوصول 🚀' ]); // FCM $passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first(); if ($passengerToken) { $this->fcm->sendToDevice( $this->encryption->decrypt($passengerToken->token), "بدأت الرحلة 🏁", "نتمنى لك رحلة آمنة ومريحة.", ['ride_id' => (string) $rideId], 'Trip is Begin' ); } return response()->json(['status' => 'success', 'message' => 'Ride started successfully']); } public function finish(Request $request, int $rideId): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $price = $request->input('price', 0); $status = 'Finished'; $ride = Ride::where('id', $rideId)->where('driver_id', $driverId)->where('status', 'Begin')->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404); } DB::connection('ride')->beginTransaction(); try { $ride->update([ 'status' => $status, 'rideTimeFinish' => now(), 'endtime' => now()->toTimeString(), 'price' => $price, ]); DB::connection('primary')->table('ride')->where('id', $rideId)->update([ 'status' => $status, 'rideTimeFinish' => now(), 'price' => $price, ]); // Driver Orders $checkOrder = DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->first(); if ($checkOrder) { DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->update(['driver_id' => $driverId, 'status' => $status, 'created_at' => now()]); } else { DB::connection('primary')->table('driver_orders')->insert(['driver_id' => $driverId, 'order_id' => (string)$rideId, 'status' => $status, 'created_at' => now()]); } // Get Driver Token for LegacyList $driverTokenRaw = DB::connection('primary')->table('driverToken')->where('captain_id', $driverId)->value('token'); $driverTokenDecrypted = $driverTokenRaw ? $this->encryption->decrypt($driverTokenRaw) : ''; $legacyList = [ (string)$driverId, (string)$rideId, (string)$driverTokenDecrypted, (string)$price ]; DB::connection('ride')->commit(); // Socket $this->socket->notifyPassenger($ride->passenger_id, [ 'ride_id' => $rideId, 'status' => 'finished', 'price' => $price, 'DriverList' => $legacyList ]); // FCM $passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first(); if ($passengerToken) { $this->fcm->sendToDevice( $this->encryption->decrypt($passengerToken->token), "تم إنهاء الرحلة 🏁", "المبلغ المطلوب: " . $price . " ل.س", ['ride_id' => (string) $rideId, 'price' => (string) $price, 'DriverList' => $legacyList], 'Driver Finish Trip' ); } return response()->json(['status' => 'success', 'message' => 'Ride finished successfully']); } catch (\Exception $e) { DB::connection('ride')->rollBack(); return response()->json(['status' => 'failure', 'message' => 'DB Error: ' . $e->getMessage()], 500); } } public function cancelByPassenger(Request $request, int $rideId): JsonResponse { $passengerId = $request->attributes->get('_jwt_user_id'); $reason = $request->input('reason', 'No reason specified'); $ride = Ride::where('id', $rideId)->where('passenger_id', $passengerId)->active()->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404); } if ($ride->status === 'Begin') { return response()->json(['status' => 'failure', 'message' => 'Cannot cancel started ride'], 400); } $driverId = $ride->driver_id; $ride->update(['status' => 'cancelled_by_passenger']); DB::connection('primary')->table('ride')->where('id', $rideId)->update(['status' => 'cancelled_by_passenger', 'updated_at' => now()]); DB::connection('primary')->table('waitingRides')->where('id', (string) $rideId)->update(['status' => 'cancelled_by_passenger']); // Driver Orders if ($driverId !== 'none' && $driverId != 0) { DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->where('driver_id', $driverId)->update(['status' => 'cancelled_by_passenger', 'notes' => $reason]); } // Log cancellation DB::connection('ride')->table('canecl')->insert([ 'driverID' => $driverId ?? 'none', 'passengerID' => $passengerId, 'rideID' => (string) $rideId, 'note' => $reason, ]); if ($driverId !== 'none' && $driverId != 0) { // Socket $this->socket->sendToLocationServer('cancel_ride', [ 'driver_id' => $driverId, 'ride_id' => $rideId, 'reason' => $reason ]); // FCM $driverToken = DriverToken::where('captain_id', $driverId)->first(); if ($driverToken) { $this->fcm->sendToDevice( $this->encryption->decrypt($driverToken->token), "إلغاء الرحلة 🚫", "قام الراكب بإلغاء الرحلة: $reason", ['ride_id' => (string) $rideId, 'reason' => $reason], 'Cancel Trip' ); } } return response()->json(['status' => 'success', 'message' => 'Ride cancelled successfully']); } public function cancelByDriver(Request $request, int $rideId): JsonResponse { $driverId = $request->attributes->get('_jwt_user_id'); $reason = $request->input('reason', 'No reason specified'); $ride = Ride::where('id', $rideId)->where('driver_id', $driverId)->active()->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404); } $ride->update(['status' => 'cancelled_by_driver', 'driver_id' => 'none']); DB::connection('primary')->table('ride')->where('id', $rideId)->update(['status' => 'cancelled_by_driver']); DB::connection('primary')->table('waitingRides')->where('id', (string) $rideId)->update(['status' => 'waiting']); // Driver Orders DB::connection('primary')->table('driver_orders')->where('order_id', (string)$rideId)->where('driver_id', $driverId)->update(['status' => 'cancelled_by_driver', 'notes' => $reason]); // Log cancellation DB::connection('ride')->table('canecl')->insert([ 'driverID' => $driverId, 'passengerID' => $ride->passenger_id, 'rideID' => (string) $rideId, 'note' => $reason, ]); // Socket to passenger $this->socket->notifyPassenger($ride->passenger_id, [ 'ride_id' => $rideId, 'status' => 'cancelled_by_driver', 'reason' => $reason ]); // FCM to passenger $passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first(); if ($passengerToken) { $this->fcm->sendToDevice( $this->encryption->decrypt($passengerToken->token), "إلغاء الرحلة 🚫", "قام السائق بإلغاء الرحلة: $reason", ['ride_id' => (string) $rideId, 'reason' => $reason], 'Cancel Trip' ); } return response()->json(['status' => 'success', 'message' => 'Ride cancelled successfully']); } public function show(Request $request, int $rideId): JsonResponse { $userId = $request->attributes->get('_jwt_user_id'); $ride = Ride::where('id', $rideId) ->where(function($q) use ($userId) { $q->where('passenger_id', $userId) ->orWhere('driver_id', $userId); }) ->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found or access denied'], 404); } return response()->json(['status' => 'success', 'message' => $ride]); } public function active(Request $request): JsonResponse { $userId = $request->attributes->get('_jwt_user_id'); $userType = $request->attributes->get('_jwt_user_type'); $query = Ride::active(); if ($userType === 'driver') { $query->forDriver($userId); } else { $query->forPassenger($userId); } $ride = $query->orderBy('id', 'desc')->first(); return response()->json([ 'status' => 'success', 'message' => $ride, ]); } public function index(Request $request): JsonResponse { $userId = $request->attributes->get('_jwt_user_id'); $userType = $request->attributes->get('_jwt_user_type'); $page = $request->input('page', 1); $limit = min($request->input('limit', 20), 50); $query = Ride::query(); if ($userType === 'driver') { $query->forDriver($userId); } else { $query->forPassenger($userId); } $rides = $query->with('passenger')->orderBy('id', 'desc') ->skip(($page - 1) * $limit) ->take($limit) ->get() ->map(function ($ride) { $ride->order_id = $ride->id; $ride->start_name = "Pickup point"; $ride->end_name = "Destination point"; $ride->price = (string) number_format($ride->price, 0, '.', ''); if ($ride->passenger) { $p = $ride->passenger; $fname = !empty($p->first_name) ? $this->encryption->decrypt($p->first_name) : ''; $lname = !empty($p->last_name) ? $this->encryption->decrypt($p->last_name) : ''; $ride->passenger_name = trim($fname . ' ' . $lname); $ride->passenger_phone = !empty($p->phone) ? $this->encryption->decrypt($p->phone) : ''; } return $ride; }); return response()->json(['status' => 'success', 'message' => $rides]); } }