185 lines
7.7 KiB
PHP
Executable File
185 lines
7.7 KiB
PHP
Executable File
<?php
|
||
// acceptRide.php
|
||
|
||
// 1. Include Database Connection
|
||
// This file connects to both the Main DB ($con) and the Ride DB ($con_ride).
|
||
require_once __DIR__ . '/../../connect.php';
|
||
|
||
// 2. Input Validation & Filtering
|
||
$rideId = filterRequest("id");
|
||
$driverId = filterRequest("driver_id");
|
||
$status = filterRequest("status"); // Expected: 'Apply' or 'accepted'
|
||
$passengerToken = filterRequest("passengerToken");
|
||
|
||
// Log incoming data for debugging
|
||
error_log("ℹ️ [ACCEPT_RIDE_TRY] RideID: '$rideId' | DriverID: '$driverId' | Status: '$status' | PToken: '$passengerToken'");
|
||
|
||
// Check if critical data is missing
|
||
if (!$rideId || !$driverId) {
|
||
error_log("⛔ [ACCEPT_RIDE_FAIL] Missing parameters.");
|
||
jsonError("Missing required parameters.");
|
||
exit;
|
||
}
|
||
|
||
|
||
|
||
try {
|
||
// =================================================================================
|
||
// 3. 🔒 ATOMIC UPDATE (The Race Condition Solver)
|
||
// We attempt to update the ride status ONLY if it is currently 'waiting'.
|
||
// This prevents two drivers from accepting the same ride simultaneously.
|
||
// We execute this on the Remote/Ride Database first ($con_ride).
|
||
// =================================================================================
|
||
$stmtRemote = $con_ride->prepare("
|
||
UPDATE `ride`
|
||
SET `status` = ?, `driver_id` = ?, `rideTimeStart` = NOW()
|
||
WHERE `id` = ? AND `status` IN ('waiting', 'wait')
|
||
");
|
||
$stmtRemote->execute([$status, $driverId, $rideId]);
|
||
|
||
// Check if the update actually changed a row.
|
||
// If rowCount > 0, IT MEANS SUCCESS! This driver won the ride.
|
||
if ($stmtRemote->rowCount() > 0) {
|
||
|
||
// 4. Synchronization: Update Local Database
|
||
// Now that we secured the ride, we update the main server's DB ($con) to match.
|
||
if (isset($con)) {
|
||
$stmtLocal = $con->prepare("UPDATE `ride` SET `driver_id` = ?, `status` = ?, `rideTimeStart` = NOW() WHERE id = ?");
|
||
$stmtLocal->execute([$driverId, $status, $rideId]);
|
||
}
|
||
|
||
// 5. Update/Insert Driver Orders Table
|
||
// This tracks the driver's history or active orders.
|
||
$checkSql = "SELECT `order_id` FROM `driver_orders` WHERE `order_id` = ?";
|
||
$checkStmt = $con->prepare($checkSql);
|
||
$checkStmt->execute([$rideId]);
|
||
|
||
if ($checkStmt->rowCount() > 0) {
|
||
// If entry exists, update it
|
||
$updateSql = "UPDATE `driver_orders` SET `driver_id` = ?, `status` = ?, `created_at` = NOW() WHERE `order_id` = ?";
|
||
$con->prepare($updateSql)->execute([$driverId, $status, $rideId]);
|
||
} else {
|
||
// If not, insert new record
|
||
$insertSql = "INSERT INTO `driver_orders` (`driver_id`, `order_id`, `created_at`, `status`) VALUES (?, ?, NOW(), ?)";
|
||
$con->prepare($insertSql)->execute([$driverId, $rideId, $status]);
|
||
}
|
||
|
||
// =================================================================
|
||
// 6. 👤 GET DRIVER INFO (For the Passenger)
|
||
// We need to fetch driver details (Car, Name, Rating) to show to the passenger.
|
||
// =================================================================
|
||
|
||
$driverInfo = [];
|
||
|
||
$sqlDetails = "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,
|
||
(SELECT ROUND(AVG(rating), 2) FROM ratingDriver WHERE driver_id = d.id) AS ratingDriver,
|
||
dt.token
|
||
FROM driver d
|
||
LEFT JOIN CarRegistration c ON c.driverID = d.id
|
||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||
WHERE d.id = ?";
|
||
|
||
$stmtDetails = $con->prepare($sqlDetails);
|
||
$stmtDetails->execute([$driverId]);
|
||
$driverRawData = $stmtDetails->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if ($driverRawData) {
|
||
// List of encrypted fields that need decryption
|
||
$fieldsToDecrypt = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token'];
|
||
|
||
foreach ($driverRawData as $key => $value) {
|
||
if (in_array($key, $fieldsToDecrypt) && !empty($value)) {
|
||
// Decrypt sensitive data
|
||
$driverInfo[$key] = $encryptionHelper->decryptData($value);
|
||
} else {
|
||
$driverInfo[$key] = $value;
|
||
}
|
||
}
|
||
|
||
// Format Full Name
|
||
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
|
||
|
||
// Default rating if null
|
||
if (empty($driverInfo['ratingDriver'])) {
|
||
$driverInfo['ratingDriver'] = "5.0";
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 7. 🔔 NOTIFY PASSENGER (Socket + FCM)
|
||
// Inform the passenger that a driver has been found.
|
||
// =================================================================
|
||
|
||
// Fetch Passenger ID based on Ride ID
|
||
$stmtPas = $con->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
||
$stmtPas->execute([$rideId]);
|
||
$passenger_id = $stmtPas->fetchColumn();
|
||
|
||
if ($passenger_id) {
|
||
// A. Send Socket Notification (Real-time update on map)
|
||
if (function_exists('notifyPassengerOnRideServer')) {
|
||
notifyPassengerOnRideServer($passenger_id, [
|
||
'status' => 'accepted',
|
||
'ride_id' => $rideId,
|
||
'driver_id' => $driverId,
|
||
'driver_info' => $driverInfo
|
||
]);
|
||
}
|
||
|
||
// B. Send FCM Notification (Push Notification)
|
||
if (!empty($passengerToken)) {
|
||
// Using the standardized FCM function
|
||
sendFCM_Internal(
|
||
$passengerToken,
|
||
"Ride Accepted 🚖", // Title
|
||
"Captain " . ($driverInfo['driverName'] ?? 'Driver') . " is coming to you.", // Body
|
||
['ride_id' => (string)$rideId, 'driver_info' => $driverInfo], // Data Payload
|
||
"Accepted Ride", // Category
|
||
false // Not a topic
|
||
);
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 8. 🧹 MARKETPLACE CLEANUP (Notify Location Server)
|
||
// Crucial Step: We tell the Location Server that this ride is taken.
|
||
// The Location Server will:
|
||
// 1. Remove the ride from Redis (geo:rides:waiting).
|
||
// 2. Broadcast 'ride_taken' to other drivers to remove it from their screens.
|
||
// =================================================================
|
||
sendToLocationServer('ride_taken_event', [
|
||
'ride_id' => $rideId,
|
||
'taken_by_driver_id' => $driverId
|
||
]);
|
||
|
||
// 9. Final Response to the Driver App
|
||
echo json_encode([
|
||
"status" => "success",
|
||
"message" => "Ride Accepted",
|
||
"data" => $driverInfo
|
||
]);
|
||
|
||
} else {
|
||
// Failure: This means rowCount was 0.
|
||
// Reason: The ride status was NOT 'waiting' (another driver took it milliseconds ago).
|
||
error_log("⛔ [ACCEPT_RIDE_FAIL] Row count 0 for RideID: '$rideId'. Status wasn't 'waiting'/'wait' or ID is wrong.");
|
||
jsonError("Ride not available (Already taken)");
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
// Handle unexpected errors
|
||
error_log("⛔ [ACCEPT_RIDE_EXCEPTION] " . $e->getMessage());
|
||
jsonError("Error: " . $e->getMessage());
|
||
}
|
||
?>
|