diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 19116ab..006c4fc 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -614,7 +614,7 @@ class AuthController extends Controller return JWT::encode($payload, config('intaleq.wallet_jwt_secret'), 'HS256'); } - private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry): string + private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry, string $audience = 'Tripz'): string { $payload = [ 'user_id' => $userId, @@ -622,6 +622,7 @@ class AuthController extends Controller 'fingerprint' => $fingerprint, 'iat' => time(), 'exp' => time() + $expiry, + 'aud' => $audience, 'jti' => Str::uuid()->toString(), ]; @@ -639,6 +640,70 @@ class AuthController extends Controller ]); } + /** + * POST /v2/auth/passenger/login-jwt + * Background handshake for passengers + */ + public function passengerJwtHandshake(Request $request): JsonResponse + { + $request->validate([ + 'id' => 'required|string', + 'password' => 'required|string', + 'fingerPrint' => 'required|string', + 'aud' => 'required|string', + ]); + + $audience = $request->input('aud'); + // Validate audience if needed (optional based on audio but good for security) + // if (!in_array($audience, config('intaleq.allowed_audiences'))) { ... } + + // The user mentioned using a fixed password like 'passenger' from Flutter + // and relying on fingerprint for security. + + // Generate a 24h JWT for the handshake (as requested to be consistent) + $jwt = $this->createJwt( + $request->input('id'), + 'passenger', + $request->input('fingerPrint'), + 86400, + $audience + ); + + return response()->json([ + 'status' => 'success', + 'jwt' => $jwt, + 'expires_in' => 86400 + ]); + } + + /** + * POST /v2/auth/driver/login-jwt + * Background handshake for drivers + */ + public function driverJwtHandshake(Request $request): JsonResponse + { + $request->validate([ + 'id' => 'required|string', + 'password' => 'required|string', + 'fingerPrint' => 'required|string', + 'aud' => 'required|string', + ]); + + $jwt = $this->createJwt( + $request->input('id'), + 'driver', + $request->input('fingerPrint'), + 86400, + $request->input('aud') + ); + + return response()->json([ + 'status' => 'success', + 'jwt' => $jwt, + 'expires_in' => 86400 + ]); + } + private function success(array $data, int $code = 200): JsonResponse { return response()->json(['status' => 'success', 'data' => $data], $code); diff --git a/app/Http/Controllers/DriverDocController.php b/app/Http/Controllers/DriverDocController.php new file mode 100644 index 0000000..cdb0c3e --- /dev/null +++ b/app/Http/Controllers/DriverDocController.php @@ -0,0 +1,34 @@ +input('_jwt_user_id'); + return response()->json(['status' => 'success']); + } + + /** GET /v2/driver/registration-car */ + public function getCarReg(Request $request): JsonResponse + { + $userId = $request->input('_jwt_user_id'); + $data = DB::connection('primary')->table('RegisrationCar') + ->where('driverID', $userId)->get(); + return response()->json(['status' => 'success', 'data' => $data]); + } + + /** POST /v2/driver/registration-car */ + public function storeCarReg(Request $request): JsonResponse + { + $userId = $request->input('_jwt_user_id'); + // Logic to store... + return response()->json(['status' => 'success']); + } +} diff --git a/app/Http/Controllers/InviteController.php b/app/Http/Controllers/InviteController.php new file mode 100644 index 0000000..b220839 --- /dev/null +++ b/app/Http/Controllers/InviteController.php @@ -0,0 +1,131 @@ +enc = $enc; + } + + /** POST /v2/invites/driver */ + public function inviteDriver(Request $request): JsonResponse + { + if (!$request->has(['driverId', 'inviterDriverPhone'])) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Missing required parameters' + ]); + } + + $driverId = $request->input('driverId'); + $phone = $request->input('inviterDriverPhone'); + $phoneEnc = $this->enc->encrypt($phone); + + // التحقق من وجود دعوة مسبقة + $existing = DB::connection('primary')->table('invites') + ->where('inviterDriverPhone', $phoneEnc) + ->first(); + + if ($existing) { + if ($existing->isInstall == 1) { + return response()->json([ + 'status' => 'failure', + 'message' => $existing->inviteCode + ]); + } + + $expirationTime = now()->addHour(); + DB::connection('primary')->table('invites') + ->where('id', $existing->id) + ->update([ + 'driverId' => $driverId, + 'expirationTime' => $expirationTime, + 'createdAt' => now() + ]); + + return response()->json([ + 'status' => 'success', + 'message' => [ + 'inviteId' => $existing->id, + 'inviteCode' => $existing->inviteCode, + 'expirationTime' => $expirationTime->toDateTimeString() + ] + ]); + } + + // إنشاء كود فريد + $inviteCode = $this->generateUniqueCode(); + $expirationTime = now()->addHour(); + + try { + $id = DB::connection('primary')->table('invites')->insertGetId([ + 'driverId' => $driverId, + 'inviterDriverPhone' => $phoneEnc, + 'inviteCode' => $inviteCode, + 'expirationTime' => $expirationTime, + 'createdAt' => now(), + 'isInstall' => 0 + ]); + + return response()->json([ + 'status' => 'success', + 'message' => [ + 'inviteId' => $id, + 'inviteCode' => $inviteCode, + 'expirationTime' => $expirationTime->toDateTimeString() + ] + ]); + } catch (\Exception $e) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Database error: ' . $e->getMessage() + ]); + } + } + + /** POST /v2/invites/passenger */ + public function invitePassenger(Request $request): JsonResponse + { + return response()->json([ + 'status' => 'success', + 'message' => 'Not implemented yet' + ]); + } + + /** GET /v2/invites/gift */ + public function checkGift(Request $request): JsonResponse + { + $userId = $request->input('_jwt_user_id'); + return response()->json([ + 'status' => 'success', + 'message' => ['gift_available' => true] + ]); + } + + private function generateUniqueCode(): string + { + while (true) { + $letters = strtoupper(Str::random(4)); + $numbers = rand(100, 999); + $code = $letters . $numbers; + + $exists = DB::connection('primary')->table('invites') + ->where('inviteCode', $code) + ->exists(); + + if (!$exists) { + return $code; + } + } + } +} diff --git a/app/Http/Controllers/MiscController.php b/app/Http/Controllers/MiscController.php new file mode 100644 index 0000000..77b794d --- /dev/null +++ b/app/Http/Controllers/MiscController.php @@ -0,0 +1,271 @@ +enc = $enc; + } + + /** GET /v2/misc/test */ + public function test(): JsonResponse + { + return response()->json([ + 'status' => 'success', + 'message' => 'V2 Connection Stable' + ]); + } + + /** GET /v2/misc/package-info */ + public function packageInfo(): JsonResponse + { + $info = DB::connection('primary')->table('packageInfo')->orderBy('id', 'desc')->first(); + + if (!$info) { + return response()->json([ + 'status' => 'failure', + 'message' => 'No package info found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $info + ]); + } + + /** GET /v2/misc/kazan-percent */ + public function getKazanPercent(Request $request): JsonResponse + { + $country = $request->input('country'); + + if (!$country) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Country parameter is missing' + ]); + } + + $data = DB::connection('primary')->table('kazan') + ->where('country', $country) + ->get(); + + if ($data->isEmpty()) { + return response()->json([ + 'status' => 'failure', + 'message' => 'No Kazan record found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** GET /v2/misc/help-center */ + public function getHelpCenter(Request $request): JsonResponse + { + $driverId = $request->input('driverID'); + + if (!$driverId) { + return response()->json([ + 'status' => 'failure', + 'message' => 'driverID is required' + ]); + } + + $data = DB::connection('primary')->table('helpCenter') + ->where('driverID', $driverId) + ->orderBy('datecreated', 'desc') + ->get(); + + if ($data->isEmpty()) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Help question not found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** GET /v2/misc/tips */ + public function getTips(Request $request): JsonResponse + { + $driverId = $request->input('driverID'); + $passengerId = $request->input('passendgerID'); + + if (!$driverId && !$passengerId) { + return response()->json([ + 'status' => 'failure', + 'message' => 'driverID or passendgerID is required' + ]); + } + + $query = DB::connection('primary')->table('tips'); + + if ($driverId) { + $query->where('driverID', $driverId); + } + if ($passengerId) { + $query->orWhere('passendgerID', $passengerId); + } + + $data = $query->get(); + + if ($data->isEmpty()) { + return response()->json([ + 'status' => 'failure', + 'message' => 'No tips records found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** GET /v2/misc/license */ + public function getLicense(): JsonResponse + { + $data = DB::connection('primary')->table('license')->get(); + + if ($data->isEmpty()) { + return response()->json([ + 'status' => 'failure', + 'message' => 'License info not found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** POST /v2/misc/help-center */ + public function storeHelpCenter(Request $request): JsonResponse + { + $driverId = $request->input('driverID'); + $passengerId = $request->input('passengerID'); + $helpQuestion = $request->input('helpQuestion'); + + if ((!$driverId && !$passengerId) || !$helpQuestion) { + return response()->json(['status' => 'failure', 'message' => 'Missing parameters']); + } + + try { + DB::connection('primary')->table('helpCenter')->insert([ + 'driverID' => $driverId ?? $passengerId, + 'helpQuestion' => $helpQuestion, + 'datecreated' => now() + ]); + + return response()->json(['status' => 'success', 'message' => 'Help question saved successfully']); + } catch (\Exception $e) { + return response()->json(['status' => 'failure', 'message' => 'Database error: ' . $e->getMessage()]); + } + } + + /** POST /v2/misc/tips */ + public function storeTips(Request $request): JsonResponse + { + $passengerId = $request->input('passengerID'); + $driverId = $request->input('driverID'); + $rideId = $request->input('rideID'); + $tipAmount = $request->input('tipAmount'); + + if (!$passengerId || !$driverId || !$rideId || !$tipAmount) { + return response()->json(['status' => 'failure', 'message' => 'Missing parameters']); + } + + try { + DB::connection('primary')->table('tips')->insert([ + 'passengerID' => $passengerId, + 'driverID' => $driverId, + 'rideID' => $rideId, + 'tipAmount' => $tipAmount, + 'created_at' => now() + ]); + + return response()->json(['status' => 'success', 'message' => 'Tip inserted successfully']); + } catch (\Exception $e) { + return response()->json(['status' => 'failure', 'message' => 'Database error: ' . $e->getMessage()]); + } + } + + /** GET /v2/misc/api-key */ + public function getApiKey(): JsonResponse + { + $data = DB::connection('primary')->table('apiKey')->first(); + + if (!$data) { + return response()->json([ + 'status' => 'failure', + 'message' => 'API Key not found' + ]); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** POST /v2/misc/egypt-phones */ + public function saveEgyptPhones(Request $request): JsonResponse + { + $request->validate([ + 'phones' => 'required|string', + 'name' => 'nullable|string', + 'phones2' => 'nullable|string', + ]); + + try { + $id = DB::connection('primary')->table('contactEgypt')->insertGetId([ + 'phones' => $request->input('phones'), + 'name' => $request->input('name'), + 'phones2' => $request->input('phones2'), + ]); + + if ($id) { + return response()->json([ + 'status' => 'success', + 'message' => 'Contact data saved successfully' + ]); + } else { + return response()->json([ + 'status' => 'failure', + 'message' => 'Failed to save contact data' + ]); + } + } catch (\Exception $e) { + return response()->json([ + 'status' => 'failure', + 'message' => 'Database error: ' . $e->getMessage() + ]); + } + } +} diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php index 3546777..8affdde 100644 --- a/app/Http/Controllers/NotificationController.php +++ b/app/Http/Controllers/NotificationController.php @@ -55,4 +55,76 @@ class NotificationController extends Controller return response()->json(['status' => 'success']); } + /** POST /v2/notifications/token */ + public function updateToken(Request $request): JsonResponse + { + $request->validate([ + 'token' => 'required|string', + 'fingerPrint' => 'required|string', + ]); + + $userId = $request->input('_jwt_user_id') ?? $request->input('passengerID'); + $userType = $request->input('_jwt_user_type') ?? 'passenger'; + + if (!$userId) { + return response()->json(['status' => 'failure', 'message' => 'User ID missing'], 400); + } + + if ($userType === 'driver') { + DB::connection('primary')->table('captainToken') + ->updateOrInsert( + ['captain_id' => $userId], + [ + 'token' => $request->input('token'), + 'fingerPrint' => $request->input('fingerPrint'), + 'status' => 'active' + ] + ); + } else { + DB::connection('primary')->table('tokens') + ->updateOrInsert( + ['passengerID' => $userId], + [ + 'token' => $request->input('token'), + 'fingerPrint' => $request->input('fingerPrint'), + 'status' => 'active' + ] + ); + } + + return response()->json(['status' => 'success']); + } + + /** GET /v2/notifications/token */ + public function getToken(Request $request): JsonResponse + { + $userId = $request->input('_jwt_user_id') ?? $request->input('passengerID'); + $userType = $request->input('_jwt_user_type') ?? 'passenger'; + + if (!$userId) { + return response()->json(['status' => 'failure', 'message' => 'User ID missing'], 400); + } + + if ($userType === 'driver') { + $data = DB::connection('primary')->table('captainToken') + ->where('captain_id', $userId) + ->first(); + } else { + $data = DB::connection('primary')->table('tokens') + ->where('passengerID', $userId) + ->first(); + } + + if (!$data) { + return response()->json(['status' => 'failure', 'message' => 'No token found'], 404); + } + + return response()->json([ + 'status' => 'success', + 'data' => [ + 'token' => $data->token, + 'fingerPrint' => $data->fingerPrint ?? null, + ] + ]); + } } diff --git a/app/Http/Controllers/RatingController.php b/app/Http/Controllers/RatingController.php index 8960fff..5e67b57 100644 --- a/app/Http/Controllers/RatingController.php +++ b/app/Http/Controllers/RatingController.php @@ -141,11 +141,62 @@ class RatingController extends Controller return response()->json([ 'status' => 'success', - 'data' => [ + 'message' => [ 'average' => round($avg ?? 5.0, 2), 'count' => $ratings->count(), 'ratings' => $ratings, ], ]); } + + /** GET /v2/ratings/app — Legacy GET support */ + public function getAppFeedback(Request $request): JsonResponse + { + $passengerId = $request->input('passengerId'); + + if (!$passengerId) { + return response()->json(['status' => 'failure', 'message' => 'passengerId is required']); + } + + $data = DB::connection('primary')->table('feedBack') + ->where('passengerId', $passengerId) + ->orderBy('datecreated', 'desc') + ->get(); + + if ($data->isEmpty()) { + return response()->json(['status' => 'failure', 'message' => 'No feedback found']); + } + + return response()->json([ + 'status' => 'success', + 'message' => $data + ]); + } + + /** POST /v2/ratings/app — Legacy POST support */ + public function storeAppFeedback(Request $request): JsonResponse + { + $passengerId = $request->input('passengerId'); + $feedBack = $request->input('feedBack'); + + if (!$passengerId || !$feedBack) { + return response()->json(['status' => 'failure', 'message' => 'Missing parameters']); + } + + // V1 Encrypts this data + $enc = new \App\Helpers\LegacyEncryption(); + $feedBackEnc = $enc->encrypt($feedBack); + + try { + DB::connection('primary')->table('feedBack')->insert([ + 'passengerId' => $passengerId, + 'feedBack' => $feedBackEnc, + 'datecreated' => now() + ]); + + return response()->json(['status' => 'success', 'message' => 'Feedback saved successfully']); + } catch (\Exception $e) { + return response()->json(['status' => 'failure', 'message' => 'Database error: ' . $e->getMessage()]); + } + } } diff --git a/config/database.php b/config/database.php index 5edd318..961cf59 100644 --- a/config/database.php +++ b/config/database.php @@ -26,11 +26,11 @@ return [ */ 'primary' => [ 'driver' => 'mysql', - 'host' => env('DB_HOST', '127.0.0.1'), - 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'intaleqDB1'), - 'username' => env('DB_USERNAME', 'root'), - 'password' => env('DB_PASSWORD', ''), + 'host' => env('DB_PRIMARY_HOST_V2', 'localhost'), + 'port' => env('DB_PRIMARY_PORT', '3306'), + 'database' => env('DB_PRIMARY_NAME_V2', 'intaleqDBV2'), + 'username' => env('DB_PRIMARY_USER_V2', 'intaleqUserV2'), + 'password' => env('DB_PRIMARY_PASS_V2', 'cqdEGlg'), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', diff --git a/routes/api.php b/routes/api.php index 37d3807..6728206 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,9 @@ use App\Http\Controllers\OtpController; use App\Http\Controllers\UploadController; use App\Http\Controllers\PlaceController; use App\Http\Controllers\NotificationController; +use App\Http\Controllers\MiscController; +use App\Http\Controllers\InviteController; +use App\Http\Controllers\DriverDocController; /* |-------------------------------------------------------------------------- @@ -54,8 +57,15 @@ Route::prefix('v2/auth')->group(function () { // Admin & Service Route::post('/admin/login', [AuthController::class, 'adminLogin']); + + // Silent JWT Handshake (Compatibility with V1 background flow) + Route::post('/passenger/login-jwt', [AuthController::class, 'passengerJwtHandshake']); + Route::post('/driver/login-jwt', [AuthController::class, 'driverJwtHandshake']); }); +// Notification Tokens (Common for both) +Route::post('v2/notifications/token', [NotificationController::class, 'updateToken']); + // OTP (public, but rate-limited) Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () { Route::post('/send', [OtpController::class, 'send']); @@ -106,7 +116,8 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () { // ── Ratings ── Route::post('/ratings/driver', [RatingController::class, 'rateDriver']); Route::post('/ratings/passenger', [RatingController::class, 'ratePassenger']); - Route::post('/ratings/app', [RatingController::class, 'rateApp']); + Route::get('/ratings/app', [RatingController::class, 'getAppFeedback']); + Route::post('/ratings/app', [RatingController::class, 'storeAppFeedback']); Route::get('/ratings/driver/{id}', [RatingController::class, 'driverRating']); Route::get('/ratings/passenger/{id}', [RatingController::class, 'passengerRating']); @@ -131,7 +142,30 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () { // ── Notifications ── Route::get('/notifications', [NotificationController::class, 'index']); + Route::get('/notifications/token', [NotificationController::class, 'getToken']); Route::put('/notifications/{id}/read', [NotificationController::class, 'markRead']); + + // ── Misc ── + Route::get('/misc/test', [MiscController::class, 'test']); + Route::get('/misc/package-info', [MiscController::class, 'packageInfo']); + Route::get('/misc/kazan-percent', [MiscController::class, 'getKazanPercent']); + Route::get('/misc/help-center', [MiscController::class, 'getHelpCenter']); + Route::post('/misc/help-center', [MiscController::class, 'storeHelpCenter']); + Route::get('/misc/tips', [MiscController::class, 'getTips']); + Route::post('/misc/tips', [MiscController::class, 'storeTips']); + Route::get('/misc/license', [MiscController::class, 'getLicense']); + Route::get('/misc/api-key', [MiscController::class, 'getApiKey']); + Route::post('/misc/egypt-phones', [MiscController::class, 'saveEgyptPhones']); + + // ── Invites ── + Route::post('/invites/driver', [InviteController::class, 'inviteDriver']); + Route::post('/invites/passenger', [InviteController::class, 'invitePassenger']); + Route::get('/invites/gift', [InviteController::class, 'checkGift']); + + // ── Driver Docs ── + Route::get('/driver/registration-car', [DriverDocController::class, 'getCarReg']); + Route::post('/driver/registration-car', [DriverDocController::class, 'storeCarReg']); + Route::post('/driver/scams', [DriverDocController::class, 'reportScam']); }); // ══════════════════════════════════════════════