json([ 'success' => true, 'languages' => [ ['code' => 'HI', 'name' => 'Hindi (हिन्दी)'], ['code' => 'SW', 'name' => 'Swahili (Kiswahili)'], ['code' => 'TL', 'name' => 'Tagalog (Wikang Tagalog)'], ['code' => 'TA', 'name' => 'Tamil (தமிழ்)'], ] ], 200); } /** * Get all master skills for worker registration/onboarding. */ public function skills() { $skills = \App\Models\Skill::all()->map(function ($skill) { return [ 'id' => $skill->id, 'name' => $skill->name, 'image_url' => $skill->image_path ? asset('storage/' . $skill->image_path) : null, ]; }); return response()->json([ 'success' => true, 'skills' => $skills ], 200); } /** * Step 1: Send OTP to worker's mobile number or email. * Accepts: phone OR email (one is required). */ public function sendOtp(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required_without:email|nullable|string|max:50|unique:workers,phone', 'email' => 'required_without:phone|nullable|email|max:255|unique:workers,email', ], [ 'phone.required_without' => 'Please provide a mobile number or email address.', 'phone.unique' => 'This mobile number is already registered.', 'email.required_without' => 'Please provide a mobile number or email address.', 'email.unique' => 'This email address is already registered.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } // Determine identifier (phone takes priority) $identifier = $request->phone ?? $request->email; $isPhone = !empty($request->phone); // Generate OTP (always 111111 in local/dev, random in production) $otp = app()->environment('production') ? strval(rand(100000, 999999)) : '111111'; // Cache OTP for 10 minutes Cache::put('worker_otp_' . $identifier, [ 'code' => $otp, 'expires_at' => now()->addMinutes(10)->timestamp, ], 600); // Send OTP via email if email provided (phone SMS would be handled by SMS gateway) if (!$isPhone && $request->email) { try { Mail::raw("Your Migrant Worker verification code is: {$otp}\n\nThis code expires in 10 minutes.", function ($m) use ($request, $otp) { $m->to($request->email) ->subject('Your Migrant Worker Verification Code'); }); } catch (\Exception $mailEx) { logger()->error('Worker OTP Mail Failure: ' . $mailEx->getMessage()); } } return response()->json([ 'success' => true, 'message' => $isPhone ? 'OTP sent to your mobile number.' : 'OTP sent to your email address.', 'identifier' => $identifier, 'otp' => app()->environment('production') ? null : $otp, // Return OTP in response in non-prod for testing convenience ], 200); } /** * Step 2: Verify OTP sent to mobile/email. * Accepts: phone OR email, otp. */ public function verifyOtp(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required_without:email|nullable|string|max:50', 'email' => 'required_without:phone|nullable|email|max:255', 'otp' => 'required|string|size:6', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'errors' => $validator->errors() ], 422); } $identifier = $request->phone ?? $request->email; $cachedData = Cache::get('worker_otp_' . $identifier); if (!$cachedData || !isset($cachedData['code']) || !isset($cachedData['expires_at'])) { return response()->json([ 'success' => false, 'message' => 'Invalid or expired verification code.' ], 400); } // Handle any dirty/legacy cache objects gracefully if (is_object($cachedData['expires_at']) || $cachedData['expires_at'] instanceof \__PHP_Incomplete_Class) { Cache::forget('worker_otp_' . $identifier); return response()->json([ 'success' => false, 'message' => 'Session expired. Please request a new verification code.' ], 400); } if ($cachedData['code'] !== $request->otp || now()->timestamp > $cachedData['expires_at']) { return response()->json([ 'success' => false, 'message' => 'Invalid or expired verification code.' ], 400); } // Mark OTP as verified in cache (used as gate for profile setup step) Cache::put('worker_otp_verified_' . $identifier, true, 3600); // 1 hour grace Cache::forget('worker_otp_' . $identifier); return response()->json([ 'success' => true, 'message' => 'Mobile number verified successfully. Please complete your profile.', 'identifier' => $identifier, ], 200); } /** * Step 3: Complete basic profile setup after OTP verification. * Accepts: phone OR email, name, nationality, skills (array of skill IDs), language. */ public function setupProfile(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required_without:email|nullable|string|max:50', 'email' => 'required_without:phone|nullable|email|max:255', 'name' => 'required|string|max:255', 'nationality' => 'required|string|max:100', 'language' => 'nullable|string', 'gender' => 'nullable|string|in:male,female,other', 'live_in_out' => 'nullable|string|in:live_in,live_out', 'preferred_location' => 'nullable|string|max:255', 'skills' => 'nullable|array', 'skills.*' => 'integer|exists:skills,id', 'fcm_token' => 'nullable|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'errors' => $validator->errors() ], 422); } $identifier = $request->phone ?? $request->email; // Ensure OTP was verified in this session if (!Cache::get('worker_otp_verified_' . $identifier)) { return response()->json([ 'success' => false, 'message' => 'OTP verification is required before profile setup.' ], 403); } // Check if already registered if ($request->phone && Worker::where('phone', $request->phone)->exists()) { return response()->json([ 'success' => false, 'message' => 'This mobile number is already registered.' ], 409); } if ($request->email && Worker::where('email', $request->email)->exists()) { return response()->json([ 'success' => false, 'message' => 'This email address is already registered.' ], 409); } try { // Build email for workers who registered by phone $email = $request->email; if (!$email && $request->phone) { $phoneClean = preg_replace('/[^0-9]/', '', $request->phone); $email = "worker.{$phoneClean}@migrant.ae"; if (Worker::where('email', $email)->exists()) { $email = "worker.{$phoneClean}." . rand(10, 99) . "@migrant.ae"; } } $apiToken = Str::random(80); $worker = DB::transaction(function () use ($request, $email, $apiToken) { $worker = Worker::create([ 'name' => $request->name, 'email' => $email, 'phone' => $request->phone ?? null, 'language' => $request->language ?? 'HI', // Default to Hindi if not selected 'password' => Hash::make(Str::random(16)), // No password at this stage 'nationality' => $request->nationality, 'gender' => $request->gender, 'live_in_out' => $request->live_in_out, 'preferred_location' => $request->preferred_location, 'age' => 25, // Default — updated later in full profile 'salary' => 1500, // Default AED — updated later 'availability' => 'Immediate', 'experience' => 'Not Specified', 'religion' => 'Not Specified', 'bio' => 'New worker on Migrant platform.', 'verified' => false, 'status' => 'active', 'api_token' => $apiToken, 'fcm_token' => $request->fcm_token, ]); // Sync skills if provided if (!empty($request->skills)) { $worker->skills()->sync($request->skills); } return $worker; }); if ($request->filled('fcm_token')) { \App\Services\FCMService::sendPushNotification( $request->fcm_token, 'Welcome to Migrant', 'Registration complete! Welcome to Migrant.' ); } // Clear the OTP verification gate Cache::forget('worker_otp_verified_' . $identifier); $worker->load(['skills']); return response()->json([ 'success' => true, 'message' => 'Registration complete! Welcome to Migrant.', 'data' => [ 'worker' => $worker, 'token' => $apiToken, ] ], 201); } catch (\Exception $e) { logger()->error('Worker Profile Setup Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Registration failed. Please try again.', 'error' => app()->environment('local', 'testing') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Unified registration — supports backward compatibility and multipart file registration. */ public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'phone' => 'required|string|max:50|unique:workers,phone', 'salary' => 'required|numeric|min:0', 'password' => 'required|string|min:6', 'skills' => 'nullable', 'language' => 'nullable|string', 'nationality' => 'nullable|string|max:100', 'age' => 'nullable|integer', 'experience' => 'nullable|string|max:100', 'in_country' => 'nullable', 'visa_status' => 'nullable|string|max:100', 'preferred_job_type' => 'nullable|string|max:100', 'gender' => 'nullable|string|in:male,female,other', 'live_in_out' => 'nullable|string|in:live_in,live_out', 'preferred_location' => 'nullable|string|max:255', 'fcm_token' => 'nullable|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $phoneClean = preg_replace('/[^0-9]/', '', $request->phone); $email = "worker.{$phoneClean}@migrant.ae"; if (Worker::where('email', $email)->exists()) { $email = "worker.{$phoneClean}." . rand(10, 99) . "@migrant.ae"; } $skillsArray = []; if ($request->has('skills')) { $skillsInput = $request->skills; if (is_array($skillsInput)) { $skillsArray = $skillsInput; } elseif (is_string($skillsInput)) { $decoded = json_decode($skillsInput, true); $skillsArray = (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) ? $decoded : array_filter(array_map('trim', explode(',', $skillsInput))); } } $inCountry = filter_var($request->input('in_country', true), FILTER_VALIDATE_BOOLEAN); $apiToken = Str::random(80); $result = DB::transaction(function () use ($request, $email, $skillsArray, $apiToken, $inCountry) { $worker = Worker::create([ 'name' => $request->name, 'email' => $email, 'phone' => $request->phone, 'language' => $request->language ?? 'HI', 'password' => Hash::make($request->password), 'nationality' => $request->nationality ?? 'Not Specified', 'gender' => $request->gender, 'live_in_out' => $request->live_in_out, 'preferred_location' => $request->preferred_location, 'age' => $request->age ?? 25, 'salary' => $request->salary, 'availability' => 'Immediate', 'experience' => $request->experience ?? 'Not Specified', 'religion' => 'Not Specified', 'bio' => 'Hardworking and reliable General Helper available for immediate hire.', 'verified' => false, 'status' => 'active', 'api_token' => $apiToken, 'in_country' => $inCountry, 'visa_status' => $inCountry ? $request->visa_status : null, 'preferred_job_type' => $request->preferred_job_type, 'fcm_token' => $request->fcm_token, ]); if (!empty($skillsArray)) { $validSkillIds = DB::table('skills')->whereIn('id', $skillsArray)->pluck('id')->toArray(); if (!empty($validSkillIds)) { $worker->skills()->sync($validSkillIds); } } return $worker; }); if ($request->filled('fcm_token')) { \App\Services\FCMService::sendPushNotification( $request->fcm_token, 'Welcome to Migrant', 'Worker registered successfully. Please upload your passport and visa documents.' ); } $result->load(['skills', 'documents']); return response()->json([ 'success' => true, 'message' => 'Worker registered successfully. Please upload your passport and visa documents.', 'data' => ['worker' => $result, 'token' => $apiToken] ], 201); } catch (\Exception $e) { logger()->error('Worker Registration Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred during worker registration. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } // ------------------------------------------------------------------------- // Document Upload Endpoints (protected — require Bearer token) // ------------------------------------------------------------------------- /** * Upload & OCR-process the worker's passport document. * POST /workers/register/passport (auth.worker) * * Calls the dedicated Passport OCR API (http://192.168.0.252:5000/passport). * Creates or updates the worker's passport document record. * Optionally syncs name, nationality, and age extracted from OCR onto the worker profile. */ public function registerPassport(Request $request) { $validator = Validator::make($request->all(), [ 'passport_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', 'sync_profile' => 'nullable|in:true,false,1,0,TRUE,FALSE', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } /** @var \App\Models\Worker $worker */ $worker = $request->attributes->get('worker'); try { $file = $request->file('passport_file'); $ocrRes = \App\Services\OcrDocumentService::extractPassportData($file); if (empty($ocrRes['success'])) { return response()->json([ 'success' => false, 'message' => 'Passport OCR extraction failed. Please upload a clear image and try again.', 'errors' => [ 'passport_file' => ['Passport OCR API could not read the document. Please try again.'] ] ], 422); } // Build document data from OCR $number = $ocrRes['document_number'] ?? ('P' . rand(1000000, 9999999)); $expiryDate = $ocrRes['expiry_date'] ?? now()->addYears(8)->toDateString(); $issueDate = !empty($ocrRes['expiry_date']) ? date('Y-m-d', strtotime($ocrRes['expiry_date'] . ' -5 years')) : now()->subYears(3)->toDateString(); // Upsert passport document (update if already exists, create if not) $worker->documents()->updateOrCreate( ['type' => 'passport'], [ 'number' => $number, 'issue_date' => $issueDate, 'expiry_date' => $expiryDate, 'ocr_accuracy' => 98.5, 'file_path' => null, ] ); // Optionally sync OCR-extracted profile fields $profileUpdates = ['verified' => true]; $syncProfile = filter_var($request->input('sync_profile', true), FILTER_VALIDATE_BOOLEAN); if ($syncProfile) { if (!empty($ocrRes['name'])) { $profileUpdates['name'] = $ocrRes['name']; } if (!empty($ocrRes['nationality'])) { $codeUpper = strtoupper($ocrRes['nationality']); $matchedNationality = \App\Models\Nationality::where('iso3', $codeUpper) ->orWhere('code', $codeUpper) ->first(); $profileUpdates['nationality'] = $matchedNationality ? $matchedNationality->name : $ocrRes['nationality']; } if (!empty($ocrRes['date_of_birth'])) { try { $dob = new \DateTime($ocrRes['date_of_birth']); $profileUpdates['age'] = (new \DateTime())->diff($dob)->y; } catch (\Exception $dateEx) { logger()->error('Passport DOB parse error: ' . $dateEx->getMessage()); } } } if (!empty($profileUpdates)) { $worker->update($profileUpdates); } $worker->load(['skills', 'documents']); $passport = $worker->documents->where('type', 'passport')->first(); $passportData = null; if ($passport) { $passportData = array_merge($passport->toArray(), [ 'document_number' => $ocrRes['document_number'], 'expiry_date' => $ocrRes['expiry_date'], 'date_of_birth' => $ocrRes['date_of_birth'] ?? null, 'nationality' => $ocrRes['nationality'] ?? null, 'name' => $ocrRes['name'] ?? null, ]); } return response()->json([ 'success' => true, 'message' => 'Passport uploaded and processed successfully.', 'data' => [ 'worker' => $worker, 'passport' => $passportData, ] ], 200); } catch (\Exception $e) { logger()->error('Worker Passport Upload Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while processing the passport. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Upload & OCR-process the worker's visa document. * POST /workers/register/visa (auth.worker) * * Calls the dedicated Visa OCR API (http://192.168.0.252:5000/visa). * Creates or updates the worker's visa document record. */ public function registerVisa(Request $request) { $validator = Validator::make($request->all(), [ 'visa_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } /** @var \App\Models\Worker $worker */ $worker = $request->attributes->get('worker'); try { $file = $request->file('visa_file'); $ocrRes = \App\Services\OcrDocumentService::extractVisaData($file); $number = $ocrRes['document_number'] ?? ('V' . rand(1000000, 9999999)); $expiryDate = $ocrRes['expiry_date'] ?? now()->addYears(2)->toDateString(); $issueDate = $ocrRes['issue_date'] ?? date('Y-m-d', strtotime($expiryDate . ' -2 years')); $accuracy = $ocrRes['ocr_accuracy'] ?? ($ocrRes['success'] ? 98.5 : 0.0); // Upsert visa document $worker->documents()->updateOrCreate( ['type' => 'visa'], [ 'number' => $number, 'issue_date' => $issueDate, 'expiry_date' => $expiryDate, 'ocr_accuracy' => $accuracy, 'file_path' => null, ] ); $worker->update(['verified' => true]); $worker->load(['skills', 'documents']); $visa = $worker->documents->where('type', 'visa')->first(); $visaData = null; if ($visa) { $visaData = array_merge($visa->toArray(), [ 'document_number' => $ocrRes['document_number'] ?? null, 'expiry_date' => $ocrRes['expiry_date'] ?? null, 'issue_date' => $ocrRes['issue_date'] ?? null, 'visa_type' => $ocrRes['visa_type'] ?? null, ]); } return response()->json([ 'success' => true, 'message' => 'Visa uploaded and processed successfully.', 'data' => [ 'worker' => $worker, 'visa' => $visaData, ] ], 200); } catch (\Exception $e) { logger()->error('Worker Visa Upload Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while processing the visa. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Authenticate a worker and return a secure Bearer token. */ public function login(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required_without:email|nullable|string', 'email' => 'required_without:phone|nullable|email', 'password' => 'required|string', 'fcm_token' => 'nullable|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $worker = $request->phone ? Worker::where('phone', $request->phone)->first() : Worker::where('email', $request->email)->first(); if (!$worker || !Hash::check($request->password, $worker->password)) { return response()->json([ 'success' => false, 'message' => 'Invalid credentials.' ], 401); } $apiToken = Str::random(80); $updateData = ['api_token' => $apiToken]; if ($request->has('fcm_token')) { $updateData['fcm_token'] = $request->fcm_token; } $worker->update($updateData); if ($request->filled('fcm_token')) { \App\Services\FCMService::sendPushNotification( $request->fcm_token, 'Successful Login', 'Worker logged in successfully.' ); } return response()->json([ 'success' => true, 'message' => 'Worker logged in successfully.', 'data' => [ 'worker' => $worker->load(['skills', 'documents']), 'token' => $apiToken ] ], 200); } catch (\Exception $e) { logger()->error('Worker Login Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred during login. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Get list of nationalities translated to requested language. * Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta). */ public function nationalities(Request $request) { $lang = strtolower($request->input('lang') ?? $request->header('Accept-Language') ?? 'en'); if (str_contains($lang, ',')) { $lang = explode(',', $lang)[0]; } if (str_contains($lang, '-')) { $lang = explode('-', $lang)[0]; } $langMap = [ 'hindi' => 'hi', 'sawahi' => 'sw', 'swahili' => 'sw', 'taglo' => 'tl', 'tagalog' => 'tl', 'tamil' => 'ta', ]; if (isset($langMap[$lang])) { $lang = $langMap[$lang]; } if (!in_array($lang, ['en', 'hi', 'sw', 'tl', 'ta'])) { $lang = 'en'; } $rawNationalities = \App\Models\Nationality::all(); $list = []; foreach ($rawNationalities as $item) { $list[] = [ 'code' => $item->code, 'name' => $item->{$lang} ?? $item->name, ]; } $search = strtolower($request->input('search') ?? $request->input('q') ?? ''); if ($search !== '') { $list = array_filter($list, function ($item) use ($search) { return stripos($item['name'], $search) !== false || stripos($item['code'], $search) !== false; }); $list = array_values($list); } // Apply Pagination $page = (int) $request->input('page', 1); $perPage = (int) $request->input('per_page', 500); if ($perPage === 15) { $perPage = 500; } $total = count($list); $offset = ($page - 1) * $perPage; $paginatedList = array_slice($list, $offset, $perPage); return response()->json([ 'success' => true, 'data' => [ 'nationalities' => $paginatedList, 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => max(1, (int) ceil($total / $perPage)), ] ], ], 200); } /** * Get list of preferred locations translated to requested language. * Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta). */ public function preferredLocations(Request $request) { $lang = strtolower($request->input('lang') ?? $request->header('Accept-Language') ?? 'en'); if (str_contains($lang, ',')) { $lang = explode(',', $lang)[0]; } if (str_contains($lang, '-')) { $lang = explode('-', $lang)[0]; } $langMap = [ 'hindi' => 'hi', 'sawahi' => 'sw', 'swahili' => 'sw', 'taglo' => 'tl', 'tagalog' => 'tl', 'tamil' => 'ta', ]; if (isset($langMap[$lang])) { $lang = $langMap[$lang]; } if (!in_array($lang, ['en', 'hi', 'sw', 'tl', 'ta'])) { $lang = 'en'; } $rawLocations = [ ['code' => 'dubai', 'name' => 'Dubai', 'hi' => 'दुबई', 'sw' => 'Dubai', 'tl' => 'Dubai', 'ta' => 'துபாய்'], ['code' => 'abu dhabi', 'name' => 'Abu Dhabi', 'hi' => 'अबू धाबी', 'sw' => 'Abu Dhabi', 'tl' => 'Abu Dhabi', 'ta' => 'அபுதாபி'], ['code' => 'oman', 'name' => 'Oman', 'hi' => 'ओमान', 'sw' => 'Omani', 'tl' => 'Oman', 'ta' => 'ஓமன்'] ]; $list = []; foreach ($rawLocations as $item) { $list[] = [ 'code' => $item['code'], 'name' => $item[$lang] ?? $item['name'], ]; } $search = strtolower($request->input('search') ?? $request->input('q') ?? ''); if ($search !== '') { $list = array_filter($list, function ($item) use ($search) { return stripos($item['name'], $search) !== false || stripos($item['code'], $search) !== false; }); $list = array_values($list); } // Apply Pagination $page = (int) $request->input('page', 1); $perPage = (int) $request->input('per_page', 15); $total = count($list); $offset = ($page - 1) * $perPage; $paginatedList = array_slice($list, $offset, $perPage); return response()->json([ 'success' => true, 'data' => [ 'preferred_locations' => $paginatedList, 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => max(1, (int) ceil($total / $perPage)), ] ], ], 200); } /** * Get list of areas for a specific preferred location translated to requested language. * Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta). */ public function preferredLocationAreas(Request $request, $location = null) { // Get location from path or query parameter $loc = strtolower(trim($location ?? $request->input('location') ?? $request->input('city') ?? '')); // Handle URL-friendly formats like abu-dhabi, abu_dhabi, abu dhabi $loc = str_replace(['_', '-'], ' ', $loc); if (empty($loc)) { return response()->json([ 'success' => false, 'message' => 'Please provide a valid location (e.g., dubai, abu dhabi, oman).' ], 400); } $lang = strtolower($request->input('lang') ?? $request->header('Accept-Language') ?? 'en'); if (str_contains($lang, ',')) { $lang = explode(',', $lang)[0]; } if (str_contains($lang, '-')) { $lang = explode('-', $lang)[0]; } $langMap = [ 'hindi' => 'hi', 'sawahi' => 'sw', 'swahili' => 'sw', 'taglo' => 'tl', 'tagalog' => 'tl', 'tamil' => 'ta', ]; if (isset($langMap[$lang])) { $lang = $langMap[$lang]; } if (!in_array($lang, ['en', 'hi', 'sw', 'tl', 'ta'])) { $lang = 'en'; } $areasMapping = [ 'dubai' => [ ['code' => 'dubai', 'name' => 'Dubai', 'hi' => 'दुबई', 'sw' => 'Dubai', 'tl' => 'Dubai', 'ta' => 'துபாய்'], ['code' => 'marina', 'name' => 'Dubai Marina', 'hi' => 'दुबई मरीना', 'sw' => 'Dubai Marina', 'tl' => 'Dubai Marina', 'ta' => 'துபாய் மெரினா'], ['code' => 'barsha', 'name' => 'Al Barsha', 'hi' => 'अल बरशा', 'sw' => 'Al Barsha', 'tl' => 'Al Barsha', 'ta' => 'அல் பர்ஷா'], ['code' => 'nahda', 'name' => 'Al Nahda', 'hi' => 'अल नहदा', 'sw' => 'Al Nahda', 'tl' => 'Al Nahda', 'ta' => 'அல் நஹ்தா'], ['code' => 'jumeirah', 'name' => 'Jumeirah', 'hi' => 'जुमेराह', 'sw' => 'Jumeirah', 'tl' => 'Jumeirah', 'ta' => 'ஜுமேரா'], ['code' => 'deira', 'name' => 'Deira', 'hi' => 'देइरा', 'sw' => 'Deira', 'tl' => 'Deira', 'ta' => 'தேரா'], ['code' => 'downtown', 'name' => 'Downtown Dubai', 'hi' => 'डाउनटाउन दुबई', 'sw' => 'Downtown Dubai', 'tl' => 'Downtown Dubai', 'ta' => 'டவுன்டவுன் துபாய்'], ['code' => 'silicon', 'name' => 'Silicon Oasis', 'hi' => 'सिलिकॉन ओएसिस', 'sw' => 'Silicon Oasis', 'tl' => 'Silicon Oasis', 'ta' => 'சிலிக்கான் ஓசிஸ்'], ['code' => 'sports', 'name' => 'Sports City', 'hi' => 'स्पोर्ट्स सिटी', 'sw' => 'Sports City', 'tl' => 'Sports City', 'ta' => 'ஸ்போர்ட்ஸ் சிட்டி'], ['code' => 'motor', 'name' => 'Motor City', 'hi' => 'मोटर सिटी', 'sw' => 'Motor City', 'tl' => 'Motor City', 'ta' => 'மோட்டார் சிட்டி'], ['code' => 'jlt', 'name' => 'JLT', 'hi' => 'JLT', 'sw' => 'JLT', 'tl' => 'JLT', 'ta' => 'ஜே.எல்.டி'], ['code' => 'jbr', 'name' => 'JBR', 'hi' => 'JBR', 'sw' => 'JBR', 'tl' => 'JBR', 'ta' => 'ஜே.பி.ஆர்'], ['code' => 'meydan', 'name' => 'Meydan', 'hi' => 'मेदान', 'sw' => 'Meydan', 'tl' => 'Meydan', 'ta' => 'மெய்தான்'], ['code' => 'ranches', 'name' => 'Arabian Ranches', 'hi' => 'अरेबियन रैंचेस', 'sw' => 'Arabian Ranches', 'tl' => 'Arabian Ranches', 'ta' => 'அரேபியன் ரான்சஸ்'], ['code' => 'bay', 'name' => 'Business Bay', 'hi' => 'बिजनेस बे', 'sw' => 'Business Bay', 'tl' => 'Business Bay', 'ta' => 'பிசினஸ் பே'], ['code' => 'mirdif', 'name' => 'Mirdif', 'hi' => 'मिर्डिफ', 'sw' => 'Mirdif', 'tl' => 'Mirdif', 'ta' => 'மிர்திஃப்'], ['code' => 'quoz', 'name' => 'Al Quoz', 'hi' => 'अल क्वोज़', 'sw' => 'Al Quoz', 'tl' => 'Al Quoz', 'ta' => 'அல் கூஸ்'], ], 'abu dhabi' => [ ['code' => 'abu dhabi', 'name' => 'Abu Dhabi City', 'hi' => 'अबू धाबी शहर', 'sw' => 'Abu Dhabi City', 'tl' => 'Abu Dhabi City', 'ta' => 'அபுதாபி நகரம்'], ['code' => 'yas', 'name' => 'Yas Island', 'hi' => 'यास द्वीप', 'sw' => 'Yas Island', 'tl' => 'Yas Island', 'ta' => 'யாஸ் தீவு'], ['code' => 'khalifa', 'name' => 'Khalifa City', 'hi' => 'खलीफा शहर', 'sw' => 'Khalifa City', 'tl' => 'Khalifa City', 'ta' => 'கலீஃபா நகரம்'], ['code' => 'reem', 'name' => 'Reem Island', 'hi' => 'रीम द्वीप', 'sw' => 'Reem Island', 'tl' => 'Reem Island', 'ta' => 'ரீம் தீவு'], ['code' => 'saadiyat', 'name' => 'Saadiyat Island', 'hi' => 'सादियात द्वीप', 'sw' => 'Saadiyat Island', 'tl' => 'Saadiyat Island', 'ta' => 'சாதியாத் தீவு'], ['code' => 'raha', 'name' => 'Al Raha', 'hi' => 'अल राहा', 'sw' => 'Al Raha', 'tl' => 'Al Raha', 'ta' => 'அல் ராஹா'], ['code' => 'mussafah', 'name' => 'Mussafah', 'hi' => 'मुसफ्फह', 'sw' => 'Mussafah', 'tl' => 'Mussafah', 'ta' => 'முஸாஃபா'], ['code' => 'zahiyah', 'name' => 'Al Zahiyah', 'hi' => 'अल ज़ाहिया', 'sw' => 'Al Zahiyah', 'tl' => 'Al Zahiyah', 'ta' => 'அல் ஜாஹியா'], ['code' => 'karamah', 'name' => 'Al Karamah', 'hi' => 'अल करमा', 'sw' => 'Al Karamah', 'tl' => 'Al Karamah', 'ta' => 'அல் கராமா'], ], 'oman' => [ ['code' => 'oman', 'name' => 'Oman', 'hi' => 'ओमान', 'sw' => 'Omani', 'tl' => 'Oman', 'ta' => 'ஓமன்'], ['code' => 'muscat', 'name' => 'Muscat', 'hi' => 'मस्कट', 'sw' => 'Muscat', 'tl' => 'Muscat', 'ta' => 'மस्कट'], ['code' => 'salalah', 'name' => 'Salalah', 'hi' => 'सलालाह', 'sw' => 'Salalah', 'tl' => 'Salalah', 'ta' => 'சலாலா'], ['code' => 'sohar', 'name' => 'Sohar', 'hi' => 'सोहर', 'sw' => 'Sohar', 'tl' => 'Sohar', 'ta' => 'சோஹார்'], ['code' => 'nizwa', 'name' => 'Nizwa', 'hi' => 'निज़वा', 'sw' => 'Nizwa', 'tl' => 'Nizwa', 'ta' => 'நிஸ்வா'], ['code' => 'sur', 'name' => 'Sur', 'hi' => 'सुर', 'sw' => 'Sur', 'tl' => 'Sur', 'ta' => 'சூர்'], ['code' => 'ibri', 'name' => 'Ibri', 'hi' => 'इबरी', 'sw' => 'Ibri', 'tl' => 'Ibri', 'ta' => 'இப்ரி'], ['code' => 'rustaq', 'name' => 'Rustaq', 'hi' => 'रुस्तक़', 'sw' => 'Rustaq', 'tl' => 'Rustaq', 'ta' => 'रुस्तक़'], ] ]; if (!isset($areasMapping[$loc])) { return response()->json([ 'success' => false, 'message' => 'Areas for the specified location were not found. Supported locations: dubai, abu dhabi, oman.' ], 404); } $rawAreas = $areasMapping[$loc]; $list = []; foreach ($rawAreas as $item) { $list[] = [ 'code' => $item['code'], 'name' => $item[$lang] ?? $item['name'], ]; } $search = strtolower($request->input('search') ?? $request->input('q') ?? ''); if ($search !== '') { $list = array_filter($list, function ($item) use ($search) { return stripos($item['name'], $search) !== false || stripos($item['code'], $search) !== false; }); $list = array_values($list); } // Apply Pagination $page = (int) $request->input('page', 1); $perPage = (int) $request->input('per_page', 15); $total = count($list); $offset = ($page - 1) * $perPage; $paginatedList = array_slice($list, $offset, $perPage); return response()->json([ 'success' => true, 'data' => [ 'location' => $loc, 'areas' => $paginatedList, 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => max(1, (int) ceil($total / $perPage)), ] ], ], 200); } /** * Get list of languages translated to requested language. * Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta). */ public function languages(Request $request) { $lang = strtolower($request->input('lang') ?? $request->header('Accept-Language') ?? 'en'); if (str_contains($lang, ',')) { $lang = explode(',', $lang)[0]; } if (str_contains($lang, '-')) { $lang = explode('-', $lang)[0]; } $langMap = [ 'hindi' => 'hi', 'sawahi' => 'sw', 'swahili' => 'sw', 'taglo' => 'tl', 'tagalog' => 'tl', 'tamil' => 'ta', ]; if (isset($langMap[$lang])) { $lang = $langMap[$lang]; } if (!in_array($lang, ['en', 'hi', 'sw', 'tl', 'ta'])) { $lang = 'en'; } $rawLanguages = [ ['code' => 'en', 'name' => 'English', 'hi' => 'अंग्रेज़ी', 'sw' => 'Kiingereza', 'tl' => 'Ingles', 'ta' => 'ஆங்கிலம்'], ['code' => 'ar', 'name' => 'Arabic', 'hi' => 'अरबी', 'sw' => 'Kiarabu', 'tl' => 'Arabe', 'ta' => 'அரபு'], ['code' => 'hi', 'name' => 'Hindi', 'hi' => 'हिन्दी', 'sw' => 'Kihindi', 'tl' => 'Hindi', 'ta' => 'இந்தி'], ['code' => 'tl', 'name' => 'Tagalog', 'hi' => 'तागालोग', 'sw' => 'Kitagalog', 'tl' => 'Tagalog', 'ta' => 'தகலாக்'], ['code' => 'sw', 'name' => 'Swahili', 'hi' => 'स्वाहिली', 'sw' => 'Kiswahili', 'tl' => 'Swahili', 'ta' => 'சுவாஹிலி'], ['code' => 'ta', 'name' => 'Tamil', 'hi' => 'तमिल', 'sw' => 'Kitamil', 'tl' => 'Tamil', 'ta' => 'தமிழ்'], ['code' => 'ur', 'name' => 'Urdu', 'hi' => 'उर्दू', 'sw' => 'Kiurdu', 'tl' => 'Urdu', 'ta' => 'உருது'], ['code' => 'bn', 'name' => 'Bengali', 'hi' => 'बंगाली', 'sw' => 'Kibengali', 'tl' => 'Bengali', 'ta' => 'பெங்காலி'], ['code' => 'ml', 'name' => 'Malayalam', 'hi' => 'मलयालम', 'sw' => 'Kimalayalam', 'tl' => 'Malayalam', 'ta' => 'மலையாளம்'], ['code' => 'ne', 'name' => 'Nepali', 'hi' => 'नेपाली', 'sw' => 'Kinepali', 'tl' => 'Nepali', 'ta' => 'நேபாளி'], ['code' => 'si', 'name' => 'Sinhala', 'hi' => 'सिंहली', 'sw' => 'Kisinhala', 'tl' => 'Sinhala', 'ta' => 'சிங்களம்'], ['code' => 'te', 'name' => 'Telugu', 'hi' => 'तेलुगु', 'sw' => 'Kitelugu', 'tl' => 'Telugu', 'ta' => 'தெலுங்கு'], ['code' => 'pa', 'name' => 'Punjabi', 'hi' => 'पंजाबी', 'sw' => 'Kipunjabi', 'tl' => 'Punjabi', 'ta' => 'பஞ்சாபி'], ['code' => 'ps', 'name' => 'Pashto', 'hi' => 'पश्तो', 'sw' => 'Kipashto', 'tl' => 'Pashto', 'ta' => 'பஷ்தூ'], ['code' => 'kn', 'name' => 'Kannada', 'hi' => 'कन्नड़', 'sw' => 'Kikannada', 'tl' => 'Kannada', 'ta' => 'கன்னடம்'], ['code' => 'mr', 'name' => 'Marathi', 'hi' => 'मराठी', 'sw' => 'Kimarathi', 'tl' => 'Marathi', 'ta' => 'மராத்தி'], ['code' => 'gu', 'name' => 'Gujarati', 'hi' => 'गुजराती', 'sw' => 'Kigujarati', 'tl' => 'Gujarati', 'ta' => 'குஜராत्ति'], ['code' => 'fr', 'name' => 'French', 'hi' => 'फ़्रांसीसी', 'sw' => 'Kifaransa', 'tl' => 'Pranses', 'ta' => 'பிரெஞ்சு'], ['code' => 'es', 'name' => 'Spanish', 'hi' => 'स्पैनिश', 'sw' => 'Kihispania', 'tl' => 'Espanyol', 'ta' => 'ஸ்பானிஷ்'], ['code' => 'zh', 'name' => 'Chinese', 'hi' => 'चीनी', 'sw' => 'Kichina', 'tl' => 'Intsik', 'ta' => 'சீனம்'], ['code' => 'ru', 'name' => 'Russian', 'hi' => 'रूसी', 'sw' => 'Kirusi', 'tl' => 'Ruso', 'ta' => 'ரஷ்யன்'], ['code' => 'id', 'name' => 'Indonesian', 'hi' => 'इंडोनेशियाई', 'sw' => 'Kiindonesia', 'tl' => 'Indonesian', 'ta' => 'இந்தோனேசியன்'], ]; $list = []; foreach ($rawLanguages as $item) { $list[] = [ 'code' => $item['code'], 'name' => $item[$lang] ?? $item['name'], ]; } $search = strtolower($request->input('search') ?? $request->input('q') ?? ''); if ($search !== '') { $list = array_filter($list, function ($item) use ($search) { return stripos($item['name'], $search) !== false || stripos($item['code'], $search) !== false; }); $list = array_values($list); } // Apply Pagination $page = (int) $request->input('page', 1); $perPage = (int) $request->input('per_page', 500); if ($perPage === 15) { $perPage = 500; } $total = count($list); $offset = ($page - 1) * $perPage; $paginatedList = array_slice($list, $offset, $perPage); return response()->json([ 'success' => true, 'data' => [ 'languages' => $paginatedList, 'pagination' => [ 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => max(1, (int) ceil($total / $perPage)), ] ], ], 200); } }