974 lines
44 KiB
PHP
974 lines
44 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Worker;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
|
|
class WorkerAuthController extends Controller
|
|
{
|
|
/**
|
|
* Get system configuration and supported languages for worker onboarding.
|
|
* Aligns with Step 2 "Select Language (HI / SW / TL / TA)".
|
|
*/
|
|
public function config()
|
|
{
|
|
return response()->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 direct JSON payloads for passport and visa.
|
|
*/
|
|
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',
|
|
'passport' => 'nullable|array',
|
|
'visa' => 'nullable|array',
|
|
]);
|
|
|
|
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) {
|
|
// Initialize fields
|
|
$name = $request->name;
|
|
$nationality = $request->nationality ?? 'Not Specified';
|
|
$age = $request->age ?? 25;
|
|
$visaStatus = $inCountry ? $request->visa_status : null;
|
|
|
|
$passportDataInput = $request->input('passport');
|
|
$visaDataInput = $request->input('visa');
|
|
|
|
// Process passport if provided
|
|
if ($passportDataInput) {
|
|
$givenNames = trim($passportDataInput['given_names'] ?? '');
|
|
$surname = trim($passportDataInput['surname'] ?? '');
|
|
$fullName = trim($givenNames . ' ' . $surname);
|
|
if (empty($fullName)) {
|
|
$fullName = $passportDataInput['name'] ?? null;
|
|
}
|
|
if ($fullName) {
|
|
$name = $fullName;
|
|
}
|
|
|
|
if (!empty($passportDataInput['nationality'])) {
|
|
$codeUpper = strtoupper($passportDataInput['nationality']);
|
|
$matchedNationality = \App\Models\Nationality::where('iso3', $codeUpper)
|
|
->orWhere('code', $codeUpper)
|
|
->first();
|
|
$nationality = $matchedNationality ? $matchedNationality->name : $passportDataInput['nationality'];
|
|
}
|
|
|
|
if (!empty($passportDataInput['date_of_birth'])) {
|
|
try {
|
|
$dob = new \DateTime($this->normaliseDateForController($passportDataInput['date_of_birth']));
|
|
$age = (new \DateTime())->diff($dob)->y;
|
|
} catch (\Exception $dateEx) {
|
|
logger()->error('Passport DOB parse error in register: ' . $dateEx->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process visa if provided
|
|
if ($visaDataInput) {
|
|
if (!empty($visaDataInput['profession'])) {
|
|
if (empty($visaStatus)) {
|
|
$visaStatus = $visaDataInput['profession'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$worker = Worker::create([
|
|
'name' => $name,
|
|
'email' => $email,
|
|
'phone' => $request->phone,
|
|
'language' => $request->language ?? 'HI',
|
|
'password' => Hash::make($request->password),
|
|
'nationality' => $nationality,
|
|
'gender' => $request->gender,
|
|
'live_in_out' => $request->live_in_out,
|
|
'preferred_location' => $request->preferred_location,
|
|
'age' => $age,
|
|
'salary' => $request->salary,
|
|
'availability' => 'Immediate',
|
|
'experience' => $request->experience ?? 'Not Specified',
|
|
'religion' => 'Not Specified',
|
|
'bio' => 'Hardworking and reliable General Helper available for immediate hire.',
|
|
'verified' => ($passportDataInput || $visaDataInput) ? true : false,
|
|
'status' => 'active',
|
|
'api_token' => $apiToken,
|
|
'in_country' => $inCountry,
|
|
'visa_status' => $visaStatus,
|
|
'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);
|
|
}
|
|
}
|
|
|
|
// Create passport document if provided
|
|
if ($passportDataInput) {
|
|
$passportNum = $passportDataInput['passport_number'] ?? ('P' . rand(1000000, 9999999));
|
|
$expiryDate = $passportDataInput['date_of_expiry'] ?? null;
|
|
$issueDate = $passportDataInput['date_of_issue'] ?? null;
|
|
|
|
$expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(8)->toDateString();
|
|
$issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : now()->subYears(3)->toDateString();
|
|
|
|
$worker->documents()->create([
|
|
'type' => 'passport',
|
|
'number' => $passportNum,
|
|
'issue_date' => $issueDateFormatted,
|
|
'expiry_date' => $expiryDateFormatted,
|
|
'ocr_accuracy' => 99.0,
|
|
'file_path' => null,
|
|
'ocr_data' => $passportDataInput,
|
|
]);
|
|
}
|
|
|
|
// Create visa document if provided
|
|
if ($visaDataInput) {
|
|
$visaNum = $visaDataInput['file_number'] ?? $visaDataInput['id_number'] ?? ('V' . rand(1000000, 9999999));
|
|
$expiryDate = $visaDataInput['expiry_date'] ?? null;
|
|
$issueDate = $visaDataInput['issue_date'] ?? null;
|
|
|
|
$expiryDateFormatted = $expiryDate ? $this->normaliseDateForController($expiryDate) : now()->addYears(2)->toDateString();
|
|
$issueDateFormatted = $issueDate ? $this->normaliseDateForController($issueDate) : now()->subYears(2)->toDateString();
|
|
|
|
$worker->documents()->create([
|
|
'type' => 'visa',
|
|
'number' => $visaNum,
|
|
'issue_date' => $issueDateFormatted,
|
|
'expiry_date' => $expiryDateFormatted,
|
|
'ocr_accuracy' => 99.0,
|
|
'file_path' => null,
|
|
'ocr_data' => $visaDataInput,
|
|
]);
|
|
}
|
|
|
|
return $worker;
|
|
});
|
|
|
|
if ($request->filled('fcm_token')) {
|
|
\App\Services\FCMService::sendPushNotification(
|
|
$request->fcm_token,
|
|
'Welcome to Migrant',
|
|
'Worker registered successfully.'
|
|
);
|
|
}
|
|
|
|
$result->load(['skills', 'documents']);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Worker registered successfully.',
|
|
'data' => ['worker' => $result, 'token' => $apiToken]
|
|
], 201);
|
|
|
|
} catch (\Exception $e) {
|
|
logger()->error('Worker Registration Failure: ' . $e->getMessage());
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Registration failed.',
|
|
'error' => app()->environment('local', 'testing') ? $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);
|
|
}
|
|
|
|
/**
|
|
* E.g. "14/05/1990" -> "1990-05-14"
|
|
*/
|
|
private function normaliseDateForController(?string $raw): ?string
|
|
{
|
|
if (empty($raw)) {
|
|
return null;
|
|
}
|
|
$raw = trim($raw);
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw)) {
|
|
return $raw;
|
|
}
|
|
try {
|
|
if (preg_match('/^(\d{1,2})[\/\- ]([A-Za-z0-9]+)[\/\- ](\d{4})$/', $raw, $m)) {
|
|
$day = str_pad($m[1], 2, '0', STR_PAD_LEFT);
|
|
$month = $m[2];
|
|
$year = $m[3];
|
|
if (is_numeric($month)) {
|
|
$month = str_pad($month, 2, '0', STR_PAD_LEFT);
|
|
return "{$year}-{$month}-{$day}";
|
|
}
|
|
$ts = strtotime("{$day} {$month} {$year}");
|
|
return $ts ? date('Y-m-d', $ts) : $raw;
|
|
}
|
|
$dt = new \DateTime($raw);
|
|
return $dt->format('Y-m-d');
|
|
} catch (\Exception $e) {
|
|
return $raw;
|
|
}
|
|
}
|
|
}
|