Security hardening: fixed 13 vulnerabilities, added AI-powered SupportController (Gemini), and stabilized Flutter Complaint logic
This commit is contained in:
@@ -189,6 +189,15 @@ class AuthController extends Controller
|
|||||||
$passenger = Passenger::find($request->input('id'));
|
$passenger = Passenger::find($request->input('id'));
|
||||||
if (!$passenger) return $this->failure('User not found');
|
if (!$passenger) return $this->failure('User not found');
|
||||||
|
|
||||||
|
// Security Check: Verify fingerprint matches stored token
|
||||||
|
$storedToken = DB::connection('primary')->table('tokens')
|
||||||
|
->where('passengerID', $passenger->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($storedToken && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) {
|
||||||
|
return $this->failure('Security mismatch: Invalid device fingerprint', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($passenger->api_key)) {
|
if (empty($passenger->api_key)) {
|
||||||
$this->generateApiKeys($passenger);
|
$this->generateApiKeys($passenger);
|
||||||
}
|
}
|
||||||
@@ -210,6 +219,15 @@ class AuthController extends Controller
|
|||||||
$driver = Driver::find($request->input('id'));
|
$driver = Driver::find($request->input('id'));
|
||||||
if (!$driver) return $this->failure('User not found');
|
if (!$driver) return $this->failure('User not found');
|
||||||
|
|
||||||
|
// Security Check: Verify fingerprint matches stored token
|
||||||
|
$storedToken = DB::connection('primary')->table('captainToken')
|
||||||
|
->where('captain_id', $driver->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($storedToken && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) {
|
||||||
|
return $this->failure('Security mismatch: Invalid device fingerprint', 403);
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($driver->api_key)) {
|
if (empty($driver->api_key)) {
|
||||||
$this->generateApiKeys($driver);
|
$this->generateApiKeys($driver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class InviteController extends Controller
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'failure',
|
'status' => 'failure',
|
||||||
'message' => 'Database error: ' . $e->getMessage()
|
'message' => 'An error occurred'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ class InviteController extends Controller
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'failure',
|
'status' => 'failure',
|
||||||
'message' => 'Database error: ' . $e->getMessage()
|
'message' => 'An error occurred'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,14 +83,7 @@ class MiscController extends Controller
|
|||||||
/** GET /v2/misc/help-center */
|
/** GET /v2/misc/help-center */
|
||||||
public function getHelpCenter(Request $request): JsonResponse
|
public function getHelpCenter(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$driverId = $request->input('driverID');
|
$driverId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
if (!$driverId) {
|
|
||||||
return response()->json([
|
|
||||||
'status' => 'failure',
|
|
||||||
'message' => 'driverID is required'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = DB::connection('primary')->table('helpCenter')
|
$data = DB::connection('primary')->table('helpCenter')
|
||||||
->where('driverID', $driverId)
|
->where('driverID', $driverId)
|
||||||
@@ -113,23 +106,15 @@ class MiscController extends Controller
|
|||||||
/** GET /v2/misc/tips */
|
/** GET /v2/misc/tips */
|
||||||
public function getTips(Request $request): JsonResponse
|
public function getTips(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$driverId = $request->input('driverID');
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
$passengerId = $request->input('passendgerID');
|
$userType = $request->attributes->get('_jwt_user_type');
|
||||||
|
|
||||||
if (!$driverId && !$passengerId) {
|
|
||||||
return response()->json([
|
|
||||||
'status' => 'failure',
|
|
||||||
'message' => 'driverID or passendgerID is required'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = DB::connection('primary')->table('tips');
|
$query = DB::connection('primary')->table('tips');
|
||||||
|
|
||||||
if ($driverId) {
|
if ($userType === 'driver') {
|
||||||
$query->where('driverID', $driverId);
|
$query->where('driverID', $userId);
|
||||||
}
|
} else {
|
||||||
if ($passengerId) {
|
$query->where('passengerID', $userId);
|
||||||
$query->orWhere('passendgerID', $passengerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $query->get();
|
$data = $query->get();
|
||||||
@@ -168,17 +153,16 @@ class MiscController extends Controller
|
|||||||
/** POST /v2/misc/help-center */
|
/** POST /v2/misc/help-center */
|
||||||
public function storeHelpCenter(Request $request): JsonResponse
|
public function storeHelpCenter(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$driverId = $request->input('driverID');
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
$passengerId = $request->input('passengerID');
|
|
||||||
$helpQuestion = $request->input('helpQuestion');
|
$helpQuestion = $request->input('helpQuestion');
|
||||||
|
|
||||||
if ((!$driverId && !$passengerId) || !$helpQuestion) {
|
if (!$helpQuestion) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Missing parameters']);
|
return response()->json(['status' => 'failure', 'message' => 'Missing help question']);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DB::connection('primary')->table('helpCenter')->insert([
|
DB::connection('primary')->table('helpCenter')->insert([
|
||||||
'driverID' => $driverId ?? $passengerId,
|
'driverID' => $userId,
|
||||||
'helpQuestion' => $helpQuestion,
|
'helpQuestion' => $helpQuestion,
|
||||||
'datecreated' => now()
|
'datecreated' => now()
|
||||||
]);
|
]);
|
||||||
@@ -193,12 +177,12 @@ class MiscController extends Controller
|
|||||||
/** POST /v2/misc/tips */
|
/** POST /v2/misc/tips */
|
||||||
public function storeTips(Request $request): JsonResponse
|
public function storeTips(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$passengerId = $request->input('passengerID');
|
$passengerId = $request->attributes->get('_jwt_user_id'); // From JWT
|
||||||
$driverId = $request->input('driverID');
|
$driverId = $request->input('driverID');
|
||||||
$rideId = $request->input('rideID');
|
$rideId = $request->input('rideID');
|
||||||
$tipAmount = $request->input('tipAmount');
|
$tipAmount = $request->input('tipAmount');
|
||||||
|
|
||||||
if (!$passengerId || !$driverId || !$rideId || !$tipAmount) {
|
if (!$driverId || !$rideId || !$tipAmount) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Missing parameters']);
|
return response()->json(['status' => 'failure', 'message' => 'Missing parameters']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ class NotificationController extends Controller
|
|||||||
$userType = $request->attributes->get('_jwt_user_type');
|
$userType = $request->attributes->get('_jwt_user_type');
|
||||||
$table = $userType === 'driver' ? 'notificationCaptain' : 'notifications';
|
$table = $userType === 'driver' ? 'notificationCaptain' : 'notifications';
|
||||||
|
|
||||||
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
|
$userField = $userType === 'driver' ? 'driverID' : 'passenger_id';
|
||||||
|
|
||||||
DB::connection('primary')->table($table)
|
DB::connection('primary')->table($table)
|
||||||
->where('id', $id)
|
->where('id', $id)
|
||||||
|
->where($userField, $userId)
|
||||||
->update(['isShown' => 'true']);
|
->update(['isShown' => 'true']);
|
||||||
|
|
||||||
return response()->json(['status' => 'success']);
|
return response()->json(['status' => 'success']);
|
||||||
@@ -82,9 +86,10 @@ class NotificationController extends Controller
|
|||||||
'affected' => $affected
|
'affected' => $affected
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error('NotificationController Update Error: ' . $e->getMessage());
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'failure',
|
'status' => 'failure',
|
||||||
'message' => 'Internal error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()
|
'message' => 'Internal server error occurred'
|
||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ class OtpController extends Controller
|
|||||||
return $this->success([
|
return $this->success([
|
||||||
'message' => 'OTP process initiated',
|
'message' => 'OTP process initiated',
|
||||||
'isRegistered' => !is_null($passenger),
|
'isRegistered' => !is_null($passenger),
|
||||||
'passenger' => $passenger,
|
|
||||||
'expires_at' => $expiration->toIso8601String(),
|
'expires_at' => $expiration->toIso8601String(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ class PromoController extends Controller
|
|||||||
$passengerId = $request->attributes->get('_jwt_user_id');
|
$passengerId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
$promos = DB::connection('primary')->table('promos')
|
$promos = DB::connection('primary')->table('promos')
|
||||||
->where('passengerID', $passengerId)
|
->where(function ($q) use ($passengerId) {
|
||||||
->orWhere('passengerID', 'none')
|
$q->where('passengerID', $passengerId)
|
||||||
|
->orWhere('passengerID', 'none');
|
||||||
|
})
|
||||||
->where(function ($q) {
|
->where(function ($q) {
|
||||||
$q->whereNull('validity_end_date')
|
$q->whereNull('validity_end_date')
|
||||||
->orWhere('validity_end_date', '>=', now()->toDateString());
|
->orWhere('validity_end_date', '>=', now()->toDateString());
|
||||||
@@ -93,8 +95,11 @@ class PromoController extends Controller
|
|||||||
/** PUT /v2/promos/{id} */
|
/** PUT /v2/promos/{id} */
|
||||||
public function update(Request $request, int $id): JsonResponse
|
public function update(Request $request, int $id): JsonResponse
|
||||||
{
|
{
|
||||||
|
$passengerId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
DB::connection('primary')->table('promos')
|
DB::connection('primary')->table('promos')
|
||||||
->where('id', $id)
|
->where('id', $id)
|
||||||
|
->where('passengerID', $passengerId)
|
||||||
->update(array_filter([
|
->update(array_filter([
|
||||||
'promo_code' => $request->input('promo_code'),
|
'promo_code' => $request->input('promo_code'),
|
||||||
'amount' => $request->input('amount'),
|
'amount' => $request->input('amount'),
|
||||||
@@ -106,9 +111,13 @@ class PromoController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** DELETE /v2/promos/{id} */
|
/** DELETE /v2/promos/{id} */
|
||||||
public function destroy(int $id): JsonResponse
|
public function destroy(Request $request, int $id): JsonResponse
|
||||||
{
|
{
|
||||||
DB::connection('primary')->table('promos')->where('id', $id)->delete();
|
$passengerId = $request->attributes->get('_jwt_user_id');
|
||||||
|
DB::connection('primary')->table('promos')
|
||||||
|
->where('id', $id)
|
||||||
|
->where('passengerID', $passengerId)
|
||||||
|
->delete();
|
||||||
return response()->json(['status' => 'success']);
|
return response()->json(['status' => 'success']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,35 +156,23 @@ class RatingController extends Controller
|
|||||||
/** GET /v2/ratings/app — Legacy GET support */
|
/** GET /v2/ratings/app — Legacy GET support */
|
||||||
public function getAppFeedback(Request $request): JsonResponse
|
public function getAppFeedback(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$passengerId = $request->input('passengerId');
|
$passengerId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
if (!$passengerId) {
|
|
||||||
return response()->json(['status' => 'failure', 'message' => 'passengerId is required']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = DB::connection('primary')->table('feedBack')
|
$data = DB::connection('primary')->table('feedBack')
|
||||||
->where('passengerId', $passengerId)
|
->where('passengerId', $passengerId)
|
||||||
->orderBy('datecreated', 'desc')
|
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
if ($data->isEmpty()) {
|
return response()->json(['status' => 'success', 'message' => $data]);
|
||||||
return response()->json(['status' => 'failure', 'message' => 'No feedback found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'status' => 'success',
|
|
||||||
'message' => $data
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** POST /v2/ratings/app — Legacy POST support */
|
/** POST /v2/ratings/app — Legacy POST support */
|
||||||
public function storeAppFeedback(Request $request): JsonResponse
|
public function storeAppFeedback(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$passengerId = $request->input('passengerId');
|
$passengerId = $request->attributes->get('_jwt_user_id');
|
||||||
$feedBack = $request->input('feedBack');
|
$feedBack = $request->input('feedBack');
|
||||||
|
|
||||||
if (!$passengerId || !$feedBack) {
|
if (!$feedBack) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Missing parameters']);
|
return response()->json(['status' => 'failure', 'message' => 'Missing feedback text']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// V1 Encrypts this data
|
// V1 Encrypts this data
|
||||||
@@ -200,7 +188,8 @@ class RatingController extends Controller
|
|||||||
|
|
||||||
return response()->json(['status' => 'success', 'message' => 'Feedback saved successfully']);
|
return response()->json(['status' => 'success', 'message' => 'Feedback saved successfully']);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Database error: ' . $e->getMessage()]);
|
\Log::error('RatingController Feedback Error: ' . $e->getMessage());
|
||||||
|
return response()->json(['status' => 'failure', 'message' => 'An error occurred while saving feedback']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,11 +505,18 @@ class RideController extends Controller
|
|||||||
* GET /v2/rides/{id}
|
* GET /v2/rides/{id}
|
||||||
* Replaces: ride/rides/getRideOrderID.php
|
* Replaces: ride/rides/getRideOrderID.php
|
||||||
*/
|
*/
|
||||||
public function show(int $rideId): JsonResponse
|
public function show(Request $request, int $rideId): JsonResponse
|
||||||
{
|
{
|
||||||
$ride = Ride::find($rideId);
|
$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) {
|
if (!$ride) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404);
|
return response()->json(['status' => 'failure', 'message' => 'Ride not found or access denied'], 404);
|
||||||
}
|
}
|
||||||
return response()->json(['status' => 'success', 'data' => $ride]);
|
return response()->json(['status' => 'success', 'data' => $ride]);
|
||||||
}
|
}
|
||||||
|
|||||||
217
app/Http/Controllers/SupportController.php
Normal file
217
app/Http/Controllers/SupportController.php
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Services\LegacyEncryption;
|
||||||
|
|
||||||
|
class SupportController extends Controller
|
||||||
|
{
|
||||||
|
protected LegacyEncryption $encryption;
|
||||||
|
|
||||||
|
public function __construct(LegacyEncryption $encryption)
|
||||||
|
{
|
||||||
|
$this->encryption = $encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /v2/support/complaints
|
||||||
|
* يحلل الشكوى باستخدام الذكاء الاصطناعي (Gemini) ويحفظ النتيجة
|
||||||
|
*/
|
||||||
|
public function storeComplaint(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'ride_id' => 'required|string',
|
||||||
|
'complaint_text' => 'required|string',
|
||||||
|
'audio_link' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rideId = $request->input('ride_id');
|
||||||
|
$complaintText = $request->input('complaint_text');
|
||||||
|
$audioLink = $request->input('audio_link');
|
||||||
|
|
||||||
|
// 1. جلب بيانات الرحلة
|
||||||
|
$ride = DB::connection('ride')->table('ride')
|
||||||
|
->where('id', $rideId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$ride) {
|
||||||
|
return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$passengerId = $ride->passenger_id;
|
||||||
|
$driverId = $ride->driver_id;
|
||||||
|
|
||||||
|
// 2. جلب الملفات التعريفية والسلوك
|
||||||
|
$passengerProfile = $this->getPassengerFullProfile($passengerId);
|
||||||
|
$driverProfile = $this->getDriverFullProfile($driverId);
|
||||||
|
$driverBehavior = DB::connection('tracking')->table('driver_behavior')
|
||||||
|
->where('trip_id', $rideId)
|
||||||
|
->where('driver_id', $driverId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// 3. بناء الـ Prompt لـ Gemini
|
||||||
|
$prompt = $this->buildGeminiPrompt($ride, $passengerProfile, $driverProfile, $driverBehavior, $complaintText, $audioLink);
|
||||||
|
|
||||||
|
// 4. استدعاء Gemini API
|
||||||
|
$apiKey = config('services.gemini.key') ?? env('GEMINI_API_KEY');
|
||||||
|
if (!$apiKey) {
|
||||||
|
Log::error('Gemini API Key missing in SupportController');
|
||||||
|
// Fallback: save without AI if key is missing?
|
||||||
|
// Better to fail if AI is expected.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Http::timeout(60)->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={$apiKey}", [
|
||||||
|
'contents' => [
|
||||||
|
['parts' => [['text' => $prompt]]]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->failed()) {
|
||||||
|
throw new \Exception('Gemini API failed: ' . $response->body());
|
||||||
|
}
|
||||||
|
|
||||||
|
$aiData = $response->json();
|
||||||
|
$analysisRaw = $aiData['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
||||||
|
$analysisJson = trim(preg_replace('/```json|```/', '', $analysisRaw));
|
||||||
|
$analysis = json_decode($analysisJson, true);
|
||||||
|
|
||||||
|
if (!$analysis || !isset($analysis['passengerReport'])) {
|
||||||
|
throw new \Exception('Failed to parse AI response: ' . $analysisRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. حفظ الشكوى والتحليل
|
||||||
|
$fullDescription = $complaintText . ($audioLink ? "\n\n[Audio Attached: {$audioLink}]" : "");
|
||||||
|
|
||||||
|
DB::connection('primary')->table('complaint')->insert([
|
||||||
|
'ride_id' => $rideId,
|
||||||
|
'passenger_id' => $passengerId,
|
||||||
|
'driver_id' => $driverId,
|
||||||
|
'complaint_type' => $analysis['complaint_type'] ?? 'General',
|
||||||
|
'description' => $fullDescription,
|
||||||
|
'statusComplaint' => 'Resolved',
|
||||||
|
'resolution' => $analysisJson,
|
||||||
|
'passenger_report' => json_encode($analysis['passengerReport'], JSON_UNESCAPED_UNICODE),
|
||||||
|
'driver_report' => json_encode($analysis['driverReport'], JSON_UNESCAPED_UNICODE),
|
||||||
|
'cs_solutions' => json_encode($analysis['customerServiceSolutions'], JSON_UNESCAPED_UNICODE),
|
||||||
|
'fault_determination' => $analysis['fault_determination'] ?? 'N/A',
|
||||||
|
'complaint_nature' => $analysis['complaint_nature'] ?? 'N/A',
|
||||||
|
'date_filed' => now(),
|
||||||
|
'date_resolved' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Complaint processed successfully.',
|
||||||
|
'passenger_response' => $analysis['passengerReport'],
|
||||||
|
'driver_response' => $analysis['driverReport']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('SupportController AI Error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
// Fallback: Just save as pending if AI fails
|
||||||
|
DB::connection('primary')->table('complaint')->insert([
|
||||||
|
'ride_id' => $rideId,
|
||||||
|
'passenger_id' => $passengerId,
|
||||||
|
'driver_id' => $driverId,
|
||||||
|
'complaint_type' => 'General',
|
||||||
|
'description' => $complaintText . ($audioLink ? " [Audio: $audioLink]" : ""),
|
||||||
|
'statusComplaint' => 'Open',
|
||||||
|
'date_filed' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Complaint received (Manual Review required).',
|
||||||
|
'passenger_response' => ['title' => 'تم استلام شكواك', 'body' => 'جاري مراجعة طلبك من قبل فريق الدعم.'],
|
||||||
|
'driver_response' => ['title' => 'بلاغ جديد', 'body' => 'تم تسجيل بلاغ بخصوص رحلتك الأخيرة.']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPassengerFullProfile($id)
|
||||||
|
{
|
||||||
|
$p = DB::connection('primary')->table('passengers')->where('id', $id)->first();
|
||||||
|
if (!$p) return null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'info' => [
|
||||||
|
'id' => $p->id,
|
||||||
|
'full_name' => trim($this->encryption->decrypt($p->first_name) . ' ' . $this->encryption->decrypt($p->last_name)),
|
||||||
|
'created_at' => $p->created_at
|
||||||
|
],
|
||||||
|
'ratings' => DB::connection('primary')->table('ratingPassenger')->where('passenger_id', $id)
|
||||||
|
->selectRaw('AVG(rating) as avg_rating, COUNT(id) as total_ratings')->first(),
|
||||||
|
'comments' => DB::connection('primary')->table('ratingPassenger')->where('passenger_id', $id)
|
||||||
|
->whereNotNull('comment')->orderBy('created_at', 'desc')->limit(5)->pluck('comment')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDriverFullProfile($id)
|
||||||
|
{
|
||||||
|
$d = DB::connection('primary')->table('driver')->where('id', $id)->first();
|
||||||
|
if (!$d) return null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'info' => [
|
||||||
|
'id' => $d->id,
|
||||||
|
'full_name' => trim($this->encryption->decrypt($d->first_name) . ' ' . $this->encryption->decrypt($d->last_name)),
|
||||||
|
'created_at' => $d->created_at
|
||||||
|
],
|
||||||
|
'ratings' => DB::connection('primary')->table('ratingDriver')->where('driver_id', $id)
|
||||||
|
->selectRaw('AVG(rating) as avg_rating, COUNT(id) as total_ratings')->first(),
|
||||||
|
'comments' => DB::connection('primary')->table('ratingDriver')->where('driver_id', $id)
|
||||||
|
->whereNotNull('comment')->orderBy('created_at', 'desc')->limit(5)->pluck('comment')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildGeminiPrompt($ride, $pProfile, $dProfile, $behavior, $text, $audio)
|
||||||
|
{
|
||||||
|
return "
|
||||||
|
أنت خبير في حل النزاعات في خدمات نقل الركاب لتطبيق intaleqapp.com. قم بتحليل الشكوى التالية بين راكب وسائق بناءً على البيانات الشاملة التالية:
|
||||||
|
|
||||||
|
**1. تفاصيل الرحلة:**
|
||||||
|
" . json_encode($ride, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "
|
||||||
|
|
||||||
|
**2. ملف الراكب:**
|
||||||
|
" . json_encode($pProfile, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "
|
||||||
|
|
||||||
|
**3. ملف السائق:**
|
||||||
|
" . json_encode($dProfile, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "
|
||||||
|
|
||||||
|
**4. بيانات سلوك السائق (في هذه الرحلة):**
|
||||||
|
" . json_encode($behavior, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "
|
||||||
|
|
||||||
|
**5. الشكوى نفسها:**
|
||||||
|
- نص الشكوى من الراكب: '" . $text . "'
|
||||||
|
- رابط تسجيل صوتي للشكوى (إن وجد): " . $audio . "
|
||||||
|
|
||||||
|
**مهمتك هي:**
|
||||||
|
1. تحليل جميع البيانات المتاحة لتحديد الطرف المخطئ على الأرجح.
|
||||||
|
2. تحديد ما إذا كانت الشكوى كيدية أم حقيقية.
|
||||||
|
3. **تصنيف الشكوى** (مثال: سلوك السائق، مشكلة في الأجرة، مسار الرحلة، حالة السيارة، أخرى).
|
||||||
|
4. اقتراح حلين واضحين ومختلفين لفريق خدمة العملاء.
|
||||||
|
5. كتابة تقرير موجز ومناسب للراكب.
|
||||||
|
6. كتابة تقرير موجز ومناسب للسائق.
|
||||||
|
|
||||||
|
**الخرج المطلوب:**
|
||||||
|
أعد الرد بصيغة JSON فقط، بدون أي نصوص إضافية، وباللغة العربية (لهجة مصرية)، بالهيكل التالي:
|
||||||
|
{
|
||||||
|
\"customerServiceSolutions\": [\"الحل المقترح الأول\", \"الحل المقترح الثاني\"],
|
||||||
|
\"passengerReport\": { \"title\": \"بخصوص شكوتك في رحلة Intaleq\", \"body\": \"رسالة واضحة للراكب بنتيجة الشكوى\" },
|
||||||
|
\"driverReport\": { \"title\": \"بخصوص بلاغ رحلتك الأخيرة في Intaleq\", \"body\": \"رسالة واضحة للسائق بنتيجة الشكوى\" },
|
||||||
|
\"fault_determination\": \"الطرف المخطئ (الراكب/السائق/كلاهما/غير واضح)\",
|
||||||
|
\"complaint_nature\": \"طبيعة الشكوى (حقيقية/كيدية/نزاع بسيط)\",
|
||||||
|
\"complaint_type\": \"تصنيف الشكوى الذي حددته\"
|
||||||
|
}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,15 +116,6 @@ class WalletController extends Controller
|
|||||||
return response()->json(['status' => 'success']);
|
return response()->json(['status' => 'success']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DELETE /v2/wallet/passenger */
|
|
||||||
public function destroy(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$id = $request->attributes->get('_jwt_user_id');
|
|
||||||
DB::connection('primary')->table('passengerWallet')
|
|
||||||
->where('passenger_id', $id)->delete();
|
|
||||||
|
|
||||||
return response()->json(['status' => 'success']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** GET /v2/wallet/passenger/transactions */
|
/** GET /v2/wallet/passenger/transactions */
|
||||||
public function transactions(Request $request): JsonResponse
|
public function transactions(Request $request): JsonResponse
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use App\Http\Controllers\NotificationController;
|
|||||||
use App\Http\Controllers\MiscController;
|
use App\Http\Controllers\MiscController;
|
||||||
use App\Http\Controllers\InviteController;
|
use App\Http\Controllers\InviteController;
|
||||||
use App\Http\Controllers\DriverDocController;
|
use App\Http\Controllers\DriverDocController;
|
||||||
|
use App\Http\Controllers\SupportController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -68,9 +69,6 @@ Route::prefix('v2/auth')->group(function () {
|
|||||||
// Admin Error Logging (public — accepts error reports from Flutter apps)
|
// Admin Error Logging (public — accepts error reports from Flutter apps)
|
||||||
Route::post('v2/admin/errors', [MiscController::class, 'logClientError']);
|
Route::post('v2/admin/errors', [MiscController::class, 'logClientError']);
|
||||||
|
|
||||||
Route::post('v2/notifications/token', [NotificationController::class, 'updateToken']);
|
|
||||||
Route::get('v2/notifications/token', [NotificationController::class, 'getToken']);
|
|
||||||
|
|
||||||
// OTP (public, but rate-limited)
|
// OTP (public, but rate-limited)
|
||||||
Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () {
|
Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () {
|
||||||
Route::post('/send', [OtpController::class, 'send']);
|
Route::post('/send', [OtpController::class, 'send']);
|
||||||
@@ -150,6 +148,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
|||||||
Route::get('/notifications', [NotificationController::class, 'index']);
|
Route::get('/notifications', [NotificationController::class, 'index']);
|
||||||
Route::post('/notifications/update', [NotificationController::class, 'updateNotification']);
|
Route::post('/notifications/update', [NotificationController::class, 'updateNotification']);
|
||||||
Route::get('/notifications/token', [NotificationController::class, 'getToken']);
|
Route::get('/notifications/token', [NotificationController::class, 'getToken']);
|
||||||
|
Route::post('/notifications/token', [NotificationController::class, 'updateToken']);
|
||||||
Route::put('/notifications/{id}/read', [NotificationController::class, 'markRead']);
|
Route::put('/notifications/{id}/read', [NotificationController::class, 'markRead']);
|
||||||
|
|
||||||
// ── Misc ──
|
// ── Misc ──
|
||||||
@@ -173,6 +172,9 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
|||||||
Route::get('/driver/registration-car', [DriverDocController::class, 'getCarReg']);
|
Route::get('/driver/registration-car', [DriverDocController::class, 'getCarReg']);
|
||||||
Route::post('/driver/registration-car', [DriverDocController::class, 'storeCarReg']);
|
Route::post('/driver/registration-car', [DriverDocController::class, 'storeCarReg']);
|
||||||
Route::post('/driver/scams', [DriverDocController::class, 'reportScam']);
|
Route::post('/driver/scams', [DriverDocController::class, 'reportScam']);
|
||||||
|
|
||||||
|
// ── Support ──
|
||||||
|
Route::post('/support/complaints', [SupportController::class, 'storeComplaint']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user