Files
intaleq_v2/app/Http/Controllers/RideController.php
2026-04-22 21:59:56 +03:00

545 lines
19 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Ride;
use App\Models\Driver;
use App\Models\DriverToken;
use App\Models\PassengerToken;
use App\Models\DriverOrder;
use App\Models\CarLocation;
use App\Helpers\LegacyEncryption;
use App\Services\FcmService;
use App\Services\SocketService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
/**
* Ride Controller
*
* Handles the complete ride lifecycle:
* create → search drivers → accept → arrive → start → finish/cancel
*/
class RideController extends Controller
{
private LegacyEncryption $encryption;
private FcmService $fcm;
private SocketService $socket;
public function __construct(LegacyEncryption $encryption, FcmService $fcm, SocketService $socket)
{
$this->encryption = $encryption;
$this->fcm = $fcm;
$this->socket = $socket;
}
/**
* POST /v2/rides
* Replaces: ride/rides/add_ride.php
*/
public function store(Request $request): JsonResponse
{
$request->validate([
'start_location' => 'required|string',
'end_location' => 'required|string',
'start_lat' => 'required|numeric',
'start_lng' => 'required|numeric',
'end_lat' => 'required|numeric',
'end_lng' => 'required|numeric',
'price' => 'required|numeric|min:0',
'car_type' => 'required|string',
'payment_method' => 'required|in:cash,visa',
'distance' => 'required|numeric',
]);
$passengerId = $request->input('_jwt_user_id');
// Prevent duplicate active rides
$activeRide = Ride::forPassenger($passengerId)->active()->first();
if ($activeRide) {
return response()->json([
'status' => 'failure',
'message' => 'You already have an active ride',
], 409);
}
// Begin transaction across both databases
DB::connection('ride')->beginTransaction();
try {
$ride = Ride::create([
'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' => 'none',
'status' => 'waiting',
'paymentMethod' => $request->input('payment_method', 'Cash'),
'carType' => $request->input('car_type', 'Speed'),
'price_for_passenger' => $request->input('price'),
'distance' => $request->input('distance'),
]);
// Also insert into waiting rides (for driver search)
DB::connection('primary')->table('waitingRides')->insert([
'id' => (string) $ride->id,
'start_location' => $request->input('start_location'),
'start_lat' => $request->input('start_lat'),
'start_lng' => $request->input('start_lng'),
'end_location' => $request->input('end_location'),
'end_lat' => $request->input('end_lat'),
'end_lng' => $request->input('end_lng'),
'date' => now()->toDateString(),
'time' => now()->toTimeString(),
'price' => $request->input('price'),
'passenger_id' => $passengerId,
'status' => 'waiting',
'carType' => $request->input('car_type', 'Speed'),
'passengerRate' => 5.0,
'price_for_passenger' => $request->input('price'),
'distance' => $request->input('distance'),
'duration' => $request->input('duration', '0'),
'payment_method' => $request->input('payment_method', 'cash'),
'passenger_wallet' => $request->input('wallet_balance', '0'),
]);
DB::connection('ride')->commit();
// Notify nearby drivers via socket
$this->socket->sendToLocationServer('new_ride', [
'ride_id' => $ride->id,
'lat' => $request->input('start_lat'),
'lng' => $request->input('start_lng'),
'car_type' => $request->input('car_type'),
]);
return response()->json([
'status' => 'success',
'data' => ['ride_id' => $ride->id],
], 201);
} catch (\Exception $e) {
DB::connection('ride')->rollBack();
return response()->json([
'status' => 'failure',
'message' => 'Failed to create ride',
], 500);
}
}
/**
* POST /v2/rides/{id}/accept
* Replaces: ride/rides/acceptRide.php
*/
public function accept(Request $request, int $rideId): JsonResponse
{
$driverId = $request->input('_jwt_user_id');
DB::connection('ride')->beginTransaction();
try {
// Lock the ride row to prevent race conditions
$ride = Ride::lockForUpdate()->find($rideId);
if (!$ride || !in_array($ride->status, ['waiting', 'wait', 'Apply'])) {
DB::connection('ride')->rollBack();
return response()->json([
'status' => 'failure',
'message' => 'Ride not available',
], 409);
}
// Update ride status atomically
$ride->update([
'driver_id' => $driverId,
'status' => 'Applied',
'DriverIsGoingToPassenger' => now(),
]);
// Update driver order
DriverOrder::where('order_id', (string) $rideId)
->where('driver_id', $driverId)
->update(['status' => 'accepted']);
// Remove from waiting rides
DB::connection('primary')
->table('waitingRides')
->where('id', (string) $rideId)
->update(['status' => 'Applied']);
// Sync to primary DB ride table
DB::connection('primary')
->table('ride')
->where('id', $rideId)
->update([
'driver_id' => $driverId,
'status' => 'Applied',
]);
DB::connection('ride')->commit();
// 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_id' => (string) $rideId, 'status' => 'Applied'],
'ride_accepted'
);
}
// Notify passenger via socket
$this->socket->notifyPassenger($ride->passenger_id, [
'ride_id' => $rideId,
'status' => 'Applied',
'driver_id' => $driverId,
]);
return response()->json(['status' => 'success']);
} catch (\Exception $e) {
DB::connection('ride')->rollBack();
return response()->json([
'status' => 'failure',
'message' => 'Failed to accept ride',
], 500);
}
}
/**
* POST /v2/rides/{id}/start
* Replaces: ride/rides/start_ride.php
*/
public function start(Request $request, int $rideId): JsonResponse
{
$driverId = $request->input('_jwt_user_id');
$ride = Ride::where('id', $rideId)
->where('driver_id', $driverId)
->whereIn('status', ['Applied', 'Arrived'])
->first();
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not found or not ready'], 404);
}
$ride->update([
'status' => 'Begin',
'rideTimeStart' => now(),
]);
// Sync to primary
DB::connection('primary')->table('ride')
->where('id', $rideId)
->update(['status' => 'Begin', 'rideTimeStart' => now()]);
// Notify passenger
$passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first();
if ($passengerToken) {
$this->fcm->sendToDevice(
$this->encryption->decrypt($passengerToken->token),
'الرحلة بدأت',
'رحلة سعيدة!',
['ride_id' => (string) $rideId, 'status' => 'Begin'],
'ride_started'
);
}
$this->socket->notifyPassenger($ride->passenger_id, [
'ride_id' => $rideId,
'status' => 'Begin',
]);
return response()->json(['status' => 'success']);
}
/**
* POST /v2/rides/{id}/arrive
* Replaces: ride/rides/arrive_ride.php
*/
public function arrive(Request $request, int $rideId): JsonResponse
{
$driverId = $request->input('_jwt_user_id');
$ride = Ride::where('id', $rideId)
->where('driver_id', $driverId)
->where('status', 'Applied')
->first();
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404);
}
$ride->update(['status' => 'Arrived']);
DB::connection('primary')->table('ride')
->where('id', $rideId)->update(['status' => 'Arrived']);
$passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first();
if ($passengerToken) {
$this->fcm->sendToDevice(
$this->encryption->decrypt($passengerToken->token),
'السائق وصل',
'السائق في انتظارك',
['ride_id' => (string) $rideId, 'status' => 'Arrived'],
'driver_arrived'
);
}
return response()->json(['status' => 'success']);
}
/**
* POST /v2/rides/{id}/finish
* Replaces: ride/rides/finish_ride_updates.php
*/
public function finish(Request $request, int $rideId): JsonResponse
{
$driverId = $request->input('_jwt_user_id');
$request->validate([
'price_for_driver' => 'required|numeric',
'price_for_passenger' => 'required|numeric',
'distance' => 'required|numeric',
]);
$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' => 'finish',
'rideTimeFinish' => now(),
'endtime' => now()->toTimeString(),
'price_for_driver' => $request->input('price_for_driver'),
'price_for_passenger' => $request->input('price_for_passenger'),
'distance' => $request->input('distance'),
]);
// Sync to primary
DB::connection('primary')->table('ride')
->where('id', $rideId)
->update([
'status' => 'finish',
'rideTimeFinish' => now(),
'price_for_driver' => $request->input('price_for_driver'),
'price_for_passenger' => $request->input('price_for_passenger'),
'distance' => $request->input('distance'),
]);
// Create payment record
DB::connection('primary')->table('payments')->insert([
'id' => uniqid('pay_'),
'amount' => $request->input('price_for_passenger'),
'payment_method' => $ride->paymentMethod,
'passengerID' => $ride->passenger_id,
'rideId' => (string) $rideId,
'driverID' => $driverId,
]);
DB::connection('ride')->commit();
// Notify passenger
$passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first();
if ($passengerToken) {
$this->fcm->sendToDevice(
$this->encryption->decrypt($passengerToken->token),
'الرحلة انتهت',
'شكراً لاستخدامك انطلق',
[
'ride_id' => (string) $rideId,
'status' => 'finish',
'price' => (string) $request->input('price_for_passenger'),
],
'ride_finished'
);
}
return response()->json(['status' => 'success']);
} catch (\Exception $e) {
DB::connection('ride')->rollBack();
return response()->json(['status' => 'failure', 'message' => 'Failed to finish ride'], 500);
}
}
/**
* POST /v2/rides/{id}/cancel/passenger
* Replaces: ride/rides/cancel_ride_by_passenger.php
*/
public function cancelByPassenger(Request $request, int $rideId): JsonResponse
{
$passengerId = $request->input('_jwt_user_id');
$ride = Ride::where('id', $rideId)
->where('passenger_id', $passengerId)
->active()
->first();
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404);
}
$ride->update(['status' => 'CancelByPassenger']);
DB::connection('primary')->table('ride')
->where('id', $rideId)->update(['status' => 'CancelByPassenger']);
DB::connection('primary')->table('waitingRides')
->where('id', (string) $rideId)->update(['status' => 'CancelByPassenger']);
// Log cancellation
DB::connection('ride')->table('canecl')->insert([
'driverID' => $ride->driver_id ?? 'none',
'passengerID' => $passengerId,
'rideID' => (string) $rideId,
'note' => $request->input('reason', 'nothing'),
]);
// Notify driver if assigned
if ($ride->driver_id !== 'none') {
$driverToken = DriverToken::where('captain_id', $ride->driver_id)->first();
if ($driverToken) {
$this->fcm->sendToDevice(
$this->encryption->decrypt($driverToken->token),
'تم إلغاء الرحلة',
'الراكب ألغى الطلب',
['ride_id' => (string) $rideId, 'status' => 'CancelByPassenger'],
'ride_cancelled'
);
}
$this->socket->sendToLocationServer('cancel_ride', [
'driver_id' => $ride->driver_id,
'ride_id' => $rideId,
]);
}
return response()->json(['status' => 'success']);
}
/**
* POST /v2/rides/{id}/cancel/driver
* Replaces: ride/rides/cancel_ride_by_driver.php
*/
public function cancelByDriver(Request $request, int $rideId): JsonResponse
{
$driverId = $request->input('_jwt_user_id');
$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' => 'CancelByDriver', 'driver_id' => 'none']);
DB::connection('primary')->table('ride')
->where('id', $rideId)->update(['status' => 'CancelByDriver']);
// Re-add to waiting rides for re-search
DB::connection('primary')->table('waitingRides')
->where('id', (string) $rideId)->update(['status' => 'waiting']);
// Log cancellation
DB::connection('ride')->table('canecl')->insert([
'driverID' => $driverId,
'passengerID' => $ride->passenger_id,
'rideID' => (string) $rideId,
'note' => $request->input('reason', 'nothing'),
]);
// Notify passenger
$passengerToken = PassengerToken::where('passengerID', $ride->passenger_id)->first();
if ($passengerToken) {
$this->fcm->sendToDevice(
$this->encryption->decrypt($passengerToken->token),
'تم إلغاء الرحلة',
'السائق ألغى الطلب، جاري البحث...',
['ride_id' => (string) $rideId, 'status' => 'CancelByDriver'],
'ride_cancelled'
);
}
return response()->json(['status' => 'success']);
}
/**
* GET /v2/rides/{id}
* Replaces: ride/rides/getRideOrderID.php
*/
public function show(int $rideId): JsonResponse
{
$ride = Ride::find($rideId);
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404);
}
return response()->json(['status' => 'success', 'data' => $ride]);
}
/**
* GET /v2/rides/active
* Replaces: ride/rides/getRideStatusFromStartApp.php
*/
public function active(Request $request): JsonResponse
{
$userId = $request->input('_jwt_user_id');
$userType = $request->input('_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',
'data' => $ride,
]);
}
/**
* GET /v2/rides
* Replaces: ride/rides/get.php
*/
public function index(Request $request): JsonResponse
{
$userId = $request->input('_jwt_user_id');
$userType = $request->input('_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->orderBy('id', 'desc')
->skip(($page - 1) * $limit)
->take($limit)
->get();
return response()->json(['status' => 'success', 'data' => $rides]);
}
}