From ed7f2f8f4ecb8284f92ee8e4ed21926f470f8649 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Fri, 19 Jun 2026 14:33:40 +0530 Subject: [PATCH] forgot and reset password --- .../Controllers/Api/SponsorAuthController.php | 125 + .../Controllers/Api/WorkerAuthController.php | 130 + .../Employer/EmployerAuthController.php | 34 +- .../Employer/PaymentController.php | 112 +- .../Middleware/EmployerGuestMiddleware.php | 34 + app/Http/Middleware/EmployerMiddleware.php | 19 +- app/Mail/ForgotPasswordOtpMail.php | 39 + bootstrap/app.php | 11 +- public/swagger.json | 10784 ++++++++-------- resources/js/Pages/Employer/Auth/Register.jsx | 177 +- .../emails/forgot-password-otp.blade.php | 49 + routes/api.php | 4 + routes/web.php | 58 +- tests/Feature/SponsorAuthWebTest.php | 5 + tests/Feature/WorkerJourneyApiTest.php | 96 + vite.config.js | 2 +- 16 files changed, 6298 insertions(+), 5381 deletions(-) create mode 100644 app/Http/Middleware/EmployerGuestMiddleware.php create mode 100644 app/Mail/ForgotPasswordOtpMail.php create mode 100644 resources/views/emails/forgot-password-otp.blade.php diff --git a/app/Http/Controllers/Api/SponsorAuthController.php b/app/Http/Controllers/Api/SponsorAuthController.php index a5e2b49..831c8b7 100644 --- a/app/Http/Controllers/Api/SponsorAuthController.php +++ b/app/Http/Controllers/Api/SponsorAuthController.php @@ -279,4 +279,129 @@ public function uploadLicense(Request $request) ], 500); } } + + // ------------------------------------------------------------------------- + // Forgot / Reset Password + // ------------------------------------------------------------------------- + + /** + * POST /api/sponsors/forgot-password + * Send a 6-digit OTP to the sponsor's registered email. + * Accepts: email OR mobile + */ + public function forgotPassword(Request $request) + { + $validator = Validator::make($request->all(), [ + 'email' => 'nullable|email', + 'mobile' => 'nullable|string', + ]); + + $validator->after(function ($v) use ($request) { + if (!$request->filled('email') && !$request->filled('mobile')) { + $v->errors()->add('email', 'Either email or mobile number is required.'); + } + }); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors(), + ], 422); + } + + $sponsor = null; + if ($request->filled('email')) { + $sponsor = Sponsor::where('email', $request->email)->first(); + } elseif ($request->filled('mobile')) { + $sponsor = Sponsor::where('mobile', $request->mobile)->first(); + } + + // Prevent email enumeration — always return success + if (!$sponsor || !$sponsor->email) { + return response()->json([ + 'success' => true, + 'message' => 'If an account with that contact exists, a reset OTP has been sent.', + ]); + } + + $otp = (string) mt_rand(100000, 999999); + $cacheKey = 'sponsor_pwd_reset_' . md5($sponsor->email); + + \Illuminate\Support\Facades\Cache::put($cacheKey, [ + 'otp' => Hash::make($otp), + 'expires_at' => now()->addMinutes(10)->timestamp, + ], now()->addMinutes(10)); + + try { + \Illuminate\Support\Facades\Mail::to($sponsor->email)->send( + new \App\Mail\ForgotPasswordOtpMail($otp, $sponsor->full_name, 'Sponsor') + ); + } catch (\Exception $e) { + logger()->error('Sponsor forgot-password mail failed: ' . $e->getMessage()); + } + + return response()->json([ + 'success' => true, + 'message' => 'If an account with that contact exists, a reset OTP has been sent.', + ]); + } + + /** + * POST /api/sponsors/reset-password + * Verify OTP and set new password. + */ + public function resetPassword(Request $request) + { + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'otp' => 'required|string|size:6', + 'password' => 'required|string|min:8|confirmed', + 'password_confirmation' => 'required|string', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors(), + ], 422); + } + + $cacheKey = 'sponsor_pwd_reset_' . md5($request->email); + $cached = \Illuminate\Support\Facades\Cache::get($cacheKey); + + if (!$cached || $cached['expires_at'] < now()->timestamp) { + \Illuminate\Support\Facades\Cache::forget($cacheKey); + return response()->json([ + 'success' => false, + 'message' => 'OTP has expired or is invalid. Please request a new one.', + ], 422); + } + + if (!Hash::check($request->otp, $cached['otp'])) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid OTP. Please check and try again.', + ], 422); + } + + $sponsor = Sponsor::where('email', $request->email)->first(); + + if (!$sponsor) { + return response()->json([ + 'success' => false, + 'message' => 'Account not found.', + ], 404); + } + + $sponsor->update(['password' => Hash::make($request->password)]); + \Illuminate\Support\Facades\Cache::forget($cacheKey); + + return response()->json([ + 'success' => true, + 'message' => 'Password reset successfully. You can now log in with your new password.', + ]); + } } + diff --git a/app/Http/Controllers/Api/WorkerAuthController.php b/app/Http/Controllers/Api/WorkerAuthController.php index 37b71dc..3d33a51 100644 --- a/app/Http/Controllers/Api/WorkerAuthController.php +++ b/app/Http/Controllers/Api/WorkerAuthController.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; +use Illuminate\Validation\Rule; class WorkerAuthController extends Controller { @@ -315,7 +316,17 @@ public function register(Request $request) 'preferred_location' => 'nullable|string|max:255', 'fcm_token' => 'nullable|string|max:255', 'passport' => 'nullable|array', + 'passport.passport_number' => [ + 'nullable', + 'string', + Rule::unique('worker_documents', 'number')->where(function ($query) { + return $query->where('type', 'passport'); + }), + ], 'visa' => 'nullable|array', + ], [ + 'phone.unique' => 'This mobile number is already registered.', + 'passport.passport_number.unique' => 'This passport number is already registered.', ]); if ($validator->fails()) { @@ -970,4 +981,123 @@ private function normaliseDateForController(?string $raw): ?string return $raw; } } + + // ------------------------------------------------------------------------- + // Forgot / Reset Password (via Mobile Number) + // ------------------------------------------------------------------------- + + /** + * POST /api/workers/forgot-password + * Send a 6-digit OTP to verify the worker's identity by phone number. + * Workers do not use email — phone is the primary identifier. + */ + public function forgotPassword(Request $request) + { + $validator = Validator::make($request->all(), [ + 'phone' => 'required|string', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors(), + ], 422); + } + + $worker = Worker::where('phone', $request->phone)->first(); + + // Always return success to prevent phone enumeration + if (!$worker) { + return response()->json([ + 'success' => true, + 'message' => 'If an account with that mobile number exists, a reset OTP has been sent.', + ]); + } + + $otp = (string) mt_rand(100000, 999999); + $cacheKey = 'worker_pwd_reset_' . md5($request->phone); + + Cache::put($cacheKey, [ + 'otp' => Hash::make($otp), + 'expires_at' => now()->addMinutes(10)->timestamp, + ], now()->addMinutes(10)); + + // Send via email if available, otherwise log for dev + if ($worker->email) { + try { + Mail::to($worker->email)->send( + new \App\Mail\ForgotPasswordOtpMail($otp, $worker->name, 'Worker') + ); + } catch (\Exception $e) { + logger()->error('Worker forgot-password mail failed: ' . $e->getMessage()); + } + } else { + // Log OTP for debugging when no email configured + logger()->info("Worker password reset OTP for {$request->phone}: {$otp}"); + } + + return response()->json([ + 'success' => true, + 'message' => 'If an account with that mobile number exists, a reset OTP has been sent.', + ]); + } + + /** + * POST /api/workers/reset-password + * Verify OTP sent to phone and set a new password. + */ + public function resetPassword(Request $request) + { + $validator = Validator::make($request->all(), [ + 'phone' => 'required|string', + 'otp' => 'required|string|size:6', + 'password' => 'required|string|min:8|confirmed', + 'password_confirmation' => 'required|string', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation error.', + 'errors' => $validator->errors(), + ], 422); + } + + $cacheKey = 'worker_pwd_reset_' . md5($request->phone); + $cached = Cache::get($cacheKey); + + if (!$cached || $cached['expires_at'] < now()->timestamp) { + Cache::forget($cacheKey); + return response()->json([ + 'success' => false, + 'message' => 'OTP has expired or is invalid. Please request a new one.', + ], 422); + } + + if (!Hash::check($request->otp, $cached['otp'])) { + return response()->json([ + 'success' => false, + 'message' => 'Invalid OTP. Please check and try again.', + ], 422); + } + + $worker = Worker::where('phone', $request->phone)->first(); + + if (!$worker) { + return response()->json([ + 'success' => false, + 'message' => 'Account not found.', + ], 404); + } + + $worker->update(['password' => Hash::make($request->password)]); + Cache::forget($cacheKey); + + return response()->json([ + 'success' => true, + 'message' => 'Password reset successfully. You can now log in with your new password.', + ]); + } } + diff --git a/app/Http/Controllers/Employer/EmployerAuthController.php b/app/Http/Controllers/Employer/EmployerAuthController.php index 563f0cf..64e1962 100644 --- a/app/Http/Controllers/Employer/EmployerAuthController.php +++ b/app/Http/Controllers/Employer/EmployerAuthController.php @@ -46,19 +46,20 @@ public function login(Request $request) return back()->with('status', 'subscription_expired'); } - // Perform standard Laravel authentication - auth()->login($user); + // Perform standard Laravel authentication (remember=true keeps auth cookie alive) + auth()->login($user, true); - session(['user' => (object)[ + // Regenerate BEFORE writing session data to avoid losing data on ID change + $request->session()->regenerate(); + + $request->session()->put('user', (object)[ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'role' => $user->role, 'subscription_status' => $user->subscription_status ?? 'active', 'verification_status' => $verification_status, - ]]); - - $request->session()->regenerate(); + ]); if ($request->source === 'mobile') { return redirect('/mobile/employer/home'); @@ -84,9 +85,11 @@ public function register(Request $request) 'company_name' => 'nullable|string|max:255', 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255', - 'phone' => 'required|string|regex:/^[0-9]{7,15}$/', + 'phone' => ['required', 'string', 'regex:/^\+?[0-9]{7,20}$/'], + 'country_code' => 'nullable|string|max:10', + 'address' => 'required|string|max:255', ], [ - 'phone.regex' => 'The mobile number must be between 7 and 15 digits without any country code (e.g. 501234567).', + 'phone.regex' => 'The mobile number must contain only digits (7-20 characters).', ]); // 2. Email uniqueness check (Check DB, return 409 if duplicate) @@ -118,6 +121,8 @@ public function register(Request $request) 'name' => $request->name, 'email' => $request->email, 'phone' => $request->phone, + 'country_code' => $request->country_code, + 'address' => $request->address, ], 'employer_otp' => [ 'hash' => $hashedOtp, @@ -433,6 +438,7 @@ public function createPassword(Request $request) 'emirates_id_front' => null, // We did not store the file 'emirates_id_number' => $pending['emirates_id_number'] ?? null, 'emirates_id_expiry' => $pending['emirates_id_expiry'] ?? null, + 'address' => $pending['address'] ?? null, ]); // Create Sponsor @@ -452,6 +458,7 @@ public function createPassword(Request $request) 'status' => 'active', 'last_login_at' => now(), 'emirates_id_file' => null, // We did not store the file + 'address' => $pending['address'] ?? null, ]); // Create active subscription in database @@ -467,17 +474,20 @@ public function createPassword(Request $request) 'updated_at' => now(), ]); - // Auto-login (Laravel + Session) - auth()->login($user); + // Auto-login (Laravel + Session) with remember=true for persistence + auth()->login($user, true); - session(['user' => (object)[ + // Regenerate session ID BEFORE writing data + request()->session()->regenerate(); + + request()->session()->put('user', (object)[ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'role' => 'employer', 'subscription_status' => 'active', 'verification_status' => 'approved', - ]]); + ]); // Flash first_login toast flag session()->flash('first_login', true); diff --git a/app/Http/Controllers/Employer/PaymentController.php b/app/Http/Controllers/Employer/PaymentController.php index 51ba8ff..7560440 100644 --- a/app/Http/Controllers/Employer/PaymentController.php +++ b/app/Http/Controllers/Employer/PaymentController.php @@ -6,90 +6,70 @@ use Illuminate\Http\Request; use Inertia\Inertia; use App\Models\User; -use App\Models\Payment; use Illuminate\Support\Facades\DB; class PaymentController extends Controller { - private function resolveCurrentUser() + private function resolveCurrentUser(): ?User { + // Prefer Laravel's built-in auth (most reliable) + if (auth()->check()) { + return auth()->user(); + } + + // Fallback: session-based user (registration auto-login flow) $sess = session('user'); $sessId = is_array($sess) ? ($sess['id'] ?? null) : ($sess->id ?? null); - - if (!$sessId) { - $user = User::where('role', 'employer')->first(); - if ($user) { - session(['user' => (object)[ - 'id' => $user->id, - 'name' => $user->name, - 'email' => $user->email, - 'role' => 'employer', - 'subscription_status' => $user->subscription_status ?? 'active', - ]]); - return $user; - } - } else { - return User::find($sessId); - } - - return null; + + return $sessId ? User::find($sessId) : null; } public function index(Request $request) { $user = $this->resolveCurrentUser(); - $employerId = $user ? $user->id : 2; - // Auto-seed payments if none exist for a richer UX - $paymentsCount = Payment::where('user_id', $employerId)->count(); - if ($paymentsCount === 0) { - $subscription = DB::table('subscriptions')->where('user_id', $employerId)->first(); - $planName = $subscription ? (ucfirst($subscription->plan_id) . ' Pass Subscription') : 'Premium Sponsor Pass Subscription'; - $planAmount = $subscription ? $subscription->amount_aed : 199.00; - - for ($i = 0; $i < 8; $i++) { - Payment::create([ - 'user_id' => $employerId, - 'amount' => $planAmount, - 'currency' => 'AED', - 'description' => $planName, - 'status' => 'success', - 'created_at' => now()->subMonths($i)->subDays(rand(1, 5)), - 'updated_at' => now()->subMonths($i)->subDays(rand(1, 5)), - ]); - } + if (!$user) { + return redirect()->route('employer.login'); } - $payments = Payment::where('user_id', $employerId) - ->where(function ($q) { - $q->where('description', 'like', '%Subscription%') - ->orWhere('description', 'like', '%Pass%'); - }) - ->latest() - ->get() - ->map(function ($p) { - return [ - 'id' => $p->id, - 'amount' => (float)$p->amount, - 'currency' => $p->currency, - 'description' => $p->description, - 'status' => $p->status, - 'date' => $p->created_at->format('M d, Y'), - 'time' => $p->created_at->format('h:i A'), - 'invoice_no' => 'INV-' . str_pad($p->id, 6, '0', STR_PAD_LEFT), - ]; - })->toArray(); + // Payments are stored in the subscriptions table during registration + $subscriptions = DB::table('subscriptions') + ->where('user_id', $user->id) + ->latest('id') + ->get(); - // Retrieve subscription details for billing and renewal analytics - $sub = DB::table('subscriptions')->where('user_id', $employerId)->where('status', 'active')->latest('id')->first(); - $expiresAt = $sub && $sub->expires_at ? date('M d, Y', strtotime($sub->expires_at)) : 'Dec 31, 2026'; - $currentPlan = $sub ? (ucfirst($sub->plan_id) . ' Sponsor Pass') : 'Premium Sponsor Pass'; - $subStatus = $user && $user->subscription_status ? ucfirst($user->subscription_status) : 'Active'; + $payments = $subscriptions->map(function ($sub) { + $planLabel = ucfirst($sub->plan_id) . ' Sponsor Pass Subscription'; + $date = \Carbon\Carbon::parse($sub->starts_at ?? $sub->created_at); + + return [ + 'id' => $sub->id, + 'amount' => (float) $sub->amount_aed, + 'currency' => 'AED', + 'description' => $planLabel, + 'status' => $sub->status === 'active' ? 'success' : $sub->status, + 'date' => $date->format('M d, Y'), + 'time' => $date->format('h:i A'), + 'invoice_no' => 'INV-' . str_pad($sub->id, 6, '0', STR_PAD_LEFT), + 'transaction_id' => $sub->paytabs_transaction_id, + 'expires_at' => $sub->expires_at ? \Carbon\Carbon::parse($sub->expires_at)->format('M d, Y') : null, + ]; + })->toArray(); + + // Latest active subscription for the summary cards + $activeSub = $subscriptions->where('status', 'active')->first(); + $expiresAt = $activeSub && $activeSub->expires_at + ? \Carbon\Carbon::parse($activeSub->expires_at)->format('M d, Y') + : null; + $currentPlan = $activeSub + ? ucfirst($activeSub->plan_id) . ' Sponsor Pass' + : null; + $subStatus = ucfirst($user->subscription_status ?? 'none'); return Inertia::render('Employer/PaymentHistory', [ - 'payments' => $payments, - 'currentPlan' => $currentPlan, - 'expiresAt' => $expiresAt, + 'payments' => $payments, + 'currentPlan' => $currentPlan, + 'expiresAt' => $expiresAt, 'subscriptionStatus' => $subStatus, ]); } diff --git a/app/Http/Middleware/EmployerGuestMiddleware.php b/app/Http/Middleware/EmployerGuestMiddleware.php new file mode 100644 index 0000000..a4f53bb --- /dev/null +++ b/app/Http/Middleware/EmployerGuestMiddleware.php @@ -0,0 +1,34 @@ +check() && auth()->user()->role === 'employer') { + return redirect()->route('employer.dashboard'); + } + + // Also check the custom session key used during registration auto-login + $sessionUser = session('user'); + $role = is_array($sessionUser) + ? ($sessionUser['role'] ?? null) + : ($sessionUser->role ?? null); + + if ($sessionUser && $role === 'employer') { + return redirect()->route('employer.dashboard'); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/EmployerMiddleware.php b/app/Http/Middleware/EmployerMiddleware.php index 2e582d5..abfa91e 100644 --- a/app/Http/Middleware/EmployerMiddleware.php +++ b/app/Http/Middleware/EmployerMiddleware.php @@ -13,12 +13,21 @@ class EmployerMiddleware */ public function handle(Request $request, Closure $next): Response { - $user = auth()->check() ? auth()->user() : session('user'); - $role = is_array($user) ? ($user['role'] ?? null) : ($user->role ?? null); - if (!$user || $role !== 'employer') { - return redirect()->route('employer.login'); + // Prefer Laravel's built-in auth (survives page refresh reliably via remember cookie) + if (auth()->check() && auth()->user()->role === 'employer') { + return $next($request); } - return $next($request); + // Fallback: check custom session key (used during registration auto-login) + $sessionUser = session('user'); + $role = is_array($sessionUser) + ? ($sessionUser['role'] ?? null) + : ($sessionUser->role ?? null); + + if ($sessionUser && $role === 'employer') { + return $next($request); + } + + return redirect()->route('employer.login'); } } diff --git a/app/Mail/ForgotPasswordOtpMail.php b/app/Mail/ForgotPasswordOtpMail.php new file mode 100644 index 0000000..d105ef5 --- /dev/null +++ b/app/Mail/ForgotPasswordOtpMail.php @@ -0,0 +1,39 @@ +otp = $otp; + $this->name = $name; + $this->userType = $userType; + } + + public function envelope(): Envelope + { + return new Envelope( + subject: 'Password Reset OTP – ' . config('app.name'), + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.forgot-password-otp', + ); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index e41e0ea..35620a9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -19,11 +19,12 @@ \App\Http\Middleware\HandleInertiaRequests::class, ]); $middleware->alias([ - 'admin' => \App\Http\Middleware\AdminMiddleware::class, - 'employer' => \App\Http\Middleware\EmployerMiddleware::class, - 'auth.worker' => \App\Http\Middleware\WorkerApiMiddleware::class, - 'auth.employer'=> \App\Http\Middleware\EmployerApiMiddleware::class, - 'auth.sponsor' => \App\Http\Middleware\SponsorApiMiddleware::class, + 'admin' => \App\Http\Middleware\AdminMiddleware::class, + 'employer' => \App\Http\Middleware\EmployerMiddleware::class, + 'employer.guest' => \App\Http\Middleware\EmployerGuestMiddleware::class, + 'auth.worker' => \App\Http\Middleware\WorkerApiMiddleware::class, + 'auth.employer' => \App\Http\Middleware\EmployerApiMiddleware::class, + 'auth.sponsor' => \App\Http\Middleware\SponsorApiMiddleware::class, ]); }) ->withExceptions(function (Exceptions $exceptions): void { diff --git a/public/swagger.json b/public/swagger.json index ca30fed..68b5a94 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -1,5273 +1,5529 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Migrant Mobile API", - "description": "Comprehensive API endpoints for the Migrant Mobile Application, covering worker registration, password login, token authentication, profile management, and interactive job offer responses.", - "version": "1.0.0" - }, - "servers": [ - { - "url": "/api", - "description": "Local development API prefix" - } - ], - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/sponsors/register": { - "post": { - "tags": [ - "Sponsor/Auth" - ], - "summary": "Register Sponsor Account", - "description": "Registers a new sponsor with basic information and uploads their organization/trade license. Sponsors have restricted access \u2014 dashboard and charity events only. No payment required.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "full_name", - "mobile", - "password", - "organization_name", - "email", - "nationality", - "city", - "address", - "country_code" - ], - "properties": { - "full_name": { - "type": "string", - "example": "Mohammed Al-Rashidi", - "description": "Full legal name of the sponsor. (REQUIRED)" - }, - "mobile": { - "type": "string", - "example": "+971501112233", - "description": "Mobile phone number — must be unique. (REQUIRED)" - }, - "password": { - "type": "string", - "format": "password", - "example": "securepass123", - "description": "Login password (min 6 characters). (REQUIRED)" - }, - "license": { - "type": "object", - "description": "Optional JSON object containing extracted organization/trade license details.", - "properties": { - "license_number": { - "type": "string", - "example": "123456" - }, - "organization_name": { - "type": "string", - "example": "Al-Rashidi Charitable Foundation" - }, - "expiry_date": { - "type": "string", - "example": "2028-06-12" - } - } - }, - "emirates_id": { - "type": "object", - "description": "Optional JSON object containing extracted Emirates ID details.", - "properties": { - "emirates_id_number": { - "type": "string", - "example": "784-1988-5310327-2" - }, - "name": { - "type": "string", - "example": "KRISHNA PRASAD" - }, - "nationality": { - "type": "string", - "example": "NPL" - }, - "date_of_birth": { - "type": "string", - "example": "1988-03-22" - }, - "expiry_date": { - "type": "string", - "example": "2028-04-11" - }, - "issue_date": { - "type": "string", - "example": "2023-04-11" - }, - "employer": { - "type": "string", - "example": "Federal Authority" - }, - "issue_place": { - "type": "string", - "example": "Abu Dhabi" - }, - "occupation": { - "type": "string", - "example": "Manager" - } - } - }, - "organization_name": { - "type": "string", - "example": "Al-Rashidi Charitable Foundation", - "description": "Name of the sponsoring organization. (REQUIRED)" - }, - "email": { - "type": "string", - "format": "email", - "example": "info@alrashidi.ae", - "description": "Email address (must be unique). (REQUIRED)" - }, - "nationality": { - "type": "string", - "example": "UAE", - "description": "Nationality of the sponsor. (REQUIRED)" - }, - "city": { - "type": "string", - "example": "Dubai", - "description": "City of residence. (REQUIRED)" - }, - "address": { - "type": "string", - "example": "Villa 12, Jumeirah 2", - "description": "Physical address. (REQUIRED)" - }, - "country_code": { - "type": "string", - "example": "+971", - "description": "Country dial code. (REQUIRED)" - }, - "license_expiry": { - "type": "string", - "format": "date", - "example": "2028-06-12", - "description": "Expiry date of the license (YYYY-MM-DD). (REQUIRED)" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Sponsor registered successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Sponsor account registered successfully. Your license is pending admin review." - }, - "data": { - "type": "object", - "properties": { - "sponsor": { - "$ref": "#/components/schemas/Sponsor" - }, - "token": { - "type": "string", - "example": "abc123...bearerToken..." - } - } - } - } - } - } - } - }, - "422": { - "description": "Validation error (duplicate mobile/email, missing required fields)." - } - } - } - }, - "/sponsors/register/license": { - "post": { - "tags": [ - "Sponsor/Auth" - ], - "summary": "Upload Sponsor Trade License", - "description": "Uploads and processes the trade license document of a Sponsor using OCR.", - "security": [], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "email", - "license_file" - ], - "properties": { - "email": { - "type": "string", - "format": "email", - "example": "info@alrashidi.ae", - "description": "Email address of the registered sponsor. (REQUIRED)" - }, - "license_file": { - "type": "string", - "format": "binary", - "description": "Trade license image/PDF file. (REQUIRED)" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Trade license processed successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "License uploaded and processed successfully." - }, - "ocr_extracted_data": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "expiry_date": { - "type": "string", - "example": "2028-06-12" - }, - "license_number": { - "type": "string", - "example": "123456" - }, - "organization_name": { - "type": "string", - "example": "Charity Co" - } - } - }, - "email": { - "type": "string", - "example": "info@alrashidi.ae" - } - } - } - } - } - }, - "404": { - "description": "Sponsor account not found." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/sponsors/login": { - "post": { - "tags": [ - "Sponsor/Auth" - ], - "summary": "Authenticate Sponsor or Employer", - "description": "Unified authentication endpoint. Validates email/mobile and password, then returns a stateless Bearer token and the user's role (employer or sponsor). Same behavior as /employers/login.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "password" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com", - "description": "Email address (required if mobile is not provided)" - }, - "mobile": { - "type": "string", - "example": "+971509990001", - "description": "Mobile number (required if email is not provided)" - }, - "password": { - "type": "string", - "example": "Password@123" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Login successful. Returns Bearer token and role.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Login successful." - }, - "role": { - "type": "string", - "example": "sponsor", - "enum": [ - "employer", - "sponsor" - ] - }, - "data": { - "type": "object", - "properties": { - "token": { - "type": "string", - "example": "api_token_here" - } - } - } - } - } - } - } - } - } - } - }, - "/sponsors/dashboard": { - "get": { - "tags": [ - "Sponsor" - ], - "summary": "Sponsor Dashboard", - "description": "Returns the sponsor profile summary, latest charity events, and total & active counts for employers and workers.", - "responses": { - "200": { - "description": "Dashboard data retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "sponsor": { - "type": "object" - }, - "recent_charity_events": { - "type": "array", - "items": { - "type": "object" - } - }, - "total_events": { - "type": "integer" - }, - "employer_stats": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 12 - }, - "active": { - "type": "integer", - "example": 8 - } - } - }, - "worker_stats": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 150 - }, - "active": { - "type": "integer", - "example": 120 - } - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthenticated." - } - } - } - }, - "/sponsors/charity-events": { - "get": { - "tags": [ - "Sponsor" - ], - "summary": "List Charity Events", - "description": "Returns a paginated list of all charity and announcement events. Optionally filter by type.", - "parameters": [ +{ + "openapi": "3.0.0", + "info": { + "title": "Migrant Mobile API", + "description": "Comprehensive API endpoints for the Migrant Mobile Application, covering worker registration, password login, token authentication, profile management, and interactive job offer responses.", + "version": "1.0.0" + }, + "servers": [ { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 15 - } - }, - { - "name": "type", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Optional event type filter (e.g. 'charity', 'update')." + "url": "/api", + "description": "Local development API prefix" } ], - "responses": { - "200": { - "description": "Events retrieved successfully." - }, - "401": { - "description": "Unauthenticated." - } - } - }, - "post": { - "tags": [ - "Sponsor" - ], - "summary": "Post Charity Event (Sponsor)", - "description": "Creates and publishes a new charity event or community drive for the authenticated sponsor.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "title", - "body", - "event_date", - "start_time", - "end_time", - "provided_items", - "location_details", - "location_pin" - ], - "properties": { - "title": { - "type": "string", - "example": "Free Dental Checkup Drive" - }, - "body": { - "type": "string", - "example": "Emirates Charity is organizing a free dental checkup for all workers this Friday morning." - }, - "type": { - "type": "string", - "enum": [ - "charity", - "info", - "warning", - "success" - ], - "example": "charity" - }, - "event_date": { - "type": "string", - "format": "date", - "example": "2026-06-15" - }, - "start_time": { - "type": "string", - "example": "09:00 AM" - }, - "end_time": { - "type": "string", - "example": "04:00 PM" - }, - "provided_items": { - "type": "string", - "example": "Free Dental screening, cleanings, and wellness kits" - }, - "location_details": { - "type": "string", - "example": "Al Quoz Community Hall, Dubai" - }, - "location_pin": { - "type": "string", - "format": "uri", - "example": "https://maps.app.goo.gl/xyz" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Charity event posted successfully." - }, - "401": { - "description": "Unauthenticated." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/sponsors/profile": { - "get": { - "tags": [ - "Sponsor" - ], - "summary": "Get Sponsor Profile", - "description": "Returns the full profile of the authenticated sponsor.", - "responses": { - "200": { - "description": "Profile retrieved successfully." - }, - "401": { - "description": "Unauthenticated." - } - } - } - }, - "/workers/register": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Register Worker Account (Step 1)", - "description": "Creates the worker account and returns a secure Bearer token.\n\nDocument upload is a **separate step** after registration:\n- `POST /workers/register/passport` \u2014 upload passport (OCR-processed)\n- `POST /workers/register/visa` \u2014 upload visa (OCR-processed)\n\nBoth document endpoints require the Bearer token returned here.", - "security": [], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "name", - "phone", - "salary", - "password" - ], - "properties": { - "name": { - "type": "string", - "example": "Amina Al-Masry", - "description": "Full legal name of the worker. (REQUIRED)" - }, - "phone": { - "type": "string", - "example": "+971509998888", - "description": "Contact mobile phone number (must be unique). (REQUIRED)" - }, - "salary": { - "type": "number", - "format": "float", - "example": 1500, - "description": "Desired minimum monthly salary in AED. (REQUIRED)" - }, - "password": { - "type": "string", - "format": "password", - "example": "securepassword123", - "description": "Secret password for subsequent mobile logins (min 6 chars). (REQUIRED)" - }, - "skills": { - "type": "array", - "items": { - "type": "integer" - }, - "example": [ - 1, - 3 - ], - "description": "Optional array of Skill IDs (can be sent as comma-separated string e.g. '1,3' or json '[1,3]' in multipart)." - }, - "language": { - "type": "string", - "example": "English, Arabic", - "description": "Languages spoken by the worker (e.g. 'English, Arabic, Hindi')." - }, - "nationality": { - "type": "string", - "example": "Egypt", - "description": "Nationality of the worker." - }, - "age": { - "type": "integer", - "example": 28, - "description": "Age of the worker." - }, - "experience": { - "type": "string", - "example": "4 Years", - "description": "Years of experience." - }, - "in_country": { - "type": "boolean", - "example": true, - "description": "Whether the worker is currently in-country (true) or out-country (false)." - }, - "visa_status": { - "type": "string", - "example": "Tourist Visa", - "description": "Visa status (only required/applicable if in_country is true)." - }, - "preferred_job_type": { - "type": "string", - "example": "full-time", - "description": "Preferred job type." - }, - "live_in_out": { - "type": "string", - "enum": [ - "live_in", - "live_out" - ], - "example": "live_out", - "description": "Accommodation preference (live_in, live_out)." - }, - "gender": { - "type": "string", - "enum": [ - "male", - "female", - "other" - ], - "example": "male", - "description": "Gender of the worker (male, female, other)." - }, - "preferred_location": { - "type": "string", - "example": "Dubai Marina", - "description": "Preferred work location/area." - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - }, - "passport": { - "type": "object", - "description": "Optional JSON object containing extracted passport details.", - "properties": { - "authority": { - "type": "string" - }, - "date_of_birth": { - "type": "string", - "example": "14/05/1990" - }, - "date_of_expiry": { - "type": "string", - "example": "05/07/2022" - }, - "date_of_issue": { - "type": "string", - "example": "05/07/2017" - }, - "document_type": { - "type": "string" - }, - "given_names": { - "type": "string" - }, - "issuing_country": { - "type": "string" - }, - "nationality": { - "type": "string" - }, - "passport_number": { - "type": "string" - }, - "personal_number": { - "type": "string" - }, - "place_of_birth": { - "type": "string" - }, - "sex": { - "type": "string" - }, - "surname": { - "type": "string" - } - } - }, - "visa": { - "type": "object", - "description": "Optional JSON object containing extracted visa details.", - "properties": { - "accompanied_by": { - "type": "string" - }, - "expiry_date": { - "type": "string", - "example": "2024-02-15" - }, - "file_number": { - "type": "string" - }, - "id_number": { - "type": "string" - }, - "issue_date": { - "type": "string", - "example": "2022-02-16" - }, - "name": { - "type": "string" - }, - "passport_number": { - "type": "string" - }, - "place_of_issue": { - "type": "string" - }, - "profession": { - "type": "string" - }, - "sponsor": { - "type": "string" - } - } - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Worker registered successfully. Use the returned token for document upload steps.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Worker registered successfully. Please upload your passport and visa documents." - }, - "data": { - "type": "object", - "properties": { - "worker": { - "$ref": "#/components/schemas/Worker" - }, - "token": { - "type": "string", - "example": "abc123xyz456...secureToken..." - } - } - } - } - } - } - } - }, - "422": { - "description": "Validation error \u2014 field validation failed or Passport OCR extraction failed.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "Validation error." - }, - "errors": { - "type": "object", - "properties": { - "phone": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "This mobile number is already registered." - ] - }, - "name": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "The name field is required." - ] - } - } - } - } - } - } - } - }, - "500": { - "description": "Server error during registration (e.g. database transaction failure).", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "An error occurred during worker registration. Please try again." - }, - "error": { - "type": "string", - "example": "Internal Server Error" - } - } - } - } - } - } - } - } - }, - "/workers/login": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Authenticate Worker", - "description": "Validates worker credentials (mobile phone number and password) and generates a secure, stateless Bearer token for api access.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone", - "password" - ], - "properties": { - "phone": { - "type": "string", - "example": "+971509998888", - "description": "Registered mobile phone number." - }, - "password": { - "type": "string", - "format": "password", - "example": "securepassword123", - "description": "Secret account password." - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Worker logged in successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Worker logged in successfully." - }, - "data": { - "type": "object", - "properties": { - "worker": { - "$ref": "#/components/schemas/Worker" - }, - "token": { - "type": "string", - "example": "abc123xyz456...secureToken..." - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthorized access.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "example": "Invalid mobile number or password." - } - } - } - } - } - } - } - } - }, - "/workers/ocr/passport-vision": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Extract Passport Data using Google Cloud Vision API", - "description": "Upload a passport image file to extract OCR data using the Google Cloud Vision API.", - "security": [], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "passport_file" - ], - "properties": { - "passport_file": { - "type": "string", - "format": "binary", - "description": "Passport image file (jpg/png/jpeg, max 10MB)." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Passport data successfully extracted.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "passport_number": { - "type": "string", - "example": "Z43R34255" - }, - "given_names": { - "type": "string", - "example": "AHMAD" - }, - "date_of_birth": { - "type": "string", - "example": "03/07/1978" - }, - "nationality": { - "type": "string", - "example": "ARE" - }, - "issuing_country": { - "type": "string", - "example": "ARE" - }, - "place_of_birth": { - "type": "string", - "example": "DUBAI" - }, - "date_of_issue": { - "type": "string", - "example": "10/02/2015" - }, - "date_of_expiry": { - "type": "string", - "example": "10/02/2020" - } - } - } - } - } - } - } - }, - "422": { - "description": "Validation error (missing passport_file)." - }, - "400": { - "description": "OCR processing error or failed Google Vision API request." - } - } - } - }, - "/workers/config": { - "get": { - "tags": [ - "Worker/Auth" - ], - "summary": "Get Supported Languages and Config", - "description": "Returns a list of supported regional languages (Hindi, Swahili, Tagalog, Tamil) for helper onboarding.", - "security": [], - "responses": { - "200": { - "description": "Config and supported languages retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "languages": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "HI" - }, - "name": { - "type": "string", - "example": "Hindi (\u0939\u093f\u0928\u094d\u0926\u0940)" - } - } - } - } - } - } - } - } - } - } - } - }, - "/workers/skills": { - "get": { - "tags": [ - "Worker/Auth" - ], - "summary": "Get Master Skills List", - "description": "Returns a list of all available master skills for helper onboarding profile setup.", - "security": [], - "responses": { - "200": { - "description": "Master skills list retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "skills": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "Cooking" - } - } - } - } - } - } - } - } - } - } - } - }, - "/nationalities": { - "get": { - "tags": [ - "General" - ], - "summary": "Get Nationalities List", - "description": "Returns a list of supported nationalities translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", - "security": [], - "parameters": [ - { - "name": "lang", - "in": "query", - "schema": { - "type": "string", - "default": "en" - }, - "description": "Language locale code (e.g. 'en', 'hindi', 'sawahi', 'taglo', 'tamil')" - }, - { - "name": "Accept-Language", - "in": "header", - "schema": { - "type": "string" - }, - "description": "Fallback header locale (e.g. 'en', 'hi', 'sw', 'tl', 'ta')" - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Search keyword to filter nationalities by name or code" - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "Page number for pagination" - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 500 - }, - "description": "Number of nationalities to return per page. Defaults to 500 on the backend to retrieve all nationalities by default." - } - ], - "responses": { - "200": { - "description": "Nationalities list retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "nationalities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "IN" - }, - "name": { - "type": "string", - "example": "Indian" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 185 - }, - "per_page": { - "type": "integer", - "example": 15 - }, - "current_page": { - "type": "integer", - "example": 1 - }, - "last_page": { - "type": "integer", - "example": 13 - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/languages": { - "get": { - "tags": [ - "General" - ], - "summary": "Get Languages List", - "description": "Returns a list of supported languages translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", - "security": [], - "parameters": [ - { - "name": "lang", - "in": "query", - "schema": { - "type": "string", - "default": "en" - }, - "description": "Language locale code (e.g. 'en', 'hindi', 'sawahi', 'taglo', 'tamil')" - }, - { - "name": "Accept-Language", - "in": "header", - "schema": { - "type": "string" - }, - "description": "Fallback header locale (e.g. 'en', 'hi', 'sw', 'tl', 'ta')" - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Search keyword to filter languages by name or code" - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "Page number for pagination" - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 500 - }, - "description": "Number of languages to return per page." - } - ], - "responses": { - "200": { - "description": "Languages list retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "languages": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "en" - }, - "name": { - "type": "string", - "example": "English" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 22 - }, - "per_page": { - "type": "integer", - "example": 15 - }, - "current_page": { - "type": "integer", - "example": 1 - }, - "last_page": { - "type": "integer", - "example": 2 - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/preferred-locations": { - "get": { - "tags": [ - "General" - ], - "summary": "Get Localized Preferred Locations List", - "description": "Returns a list of preferred locations (Dubai, Abu Dhabi, Oman) translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", - "security": [], - "parameters": [ - { - "name": "lang", - "in": "query", - "schema": { - "type": "string", - "default": "en" - }, - "description": "Language locale code (e.g. 'en', 'hindi', 'sawahi', 'taglo', 'tamil')" - }, - { - "name": "Accept-Language", - "in": "header", - "schema": { - "type": "string" - }, - "description": "Fallback header locale (e.g. 'en', 'hi', 'sw', 'tl', 'ta')" - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Search keyword to filter locations by name or code" - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "Page number for pagination" - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 15 - }, - "description": "Number of locations to return per page" - } - ], - "responses": { - "200": { - "description": "Preferred locations list retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "preferred_locations": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "dubai" - }, - "name": { - "type": "string", - "example": "Dubai" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 3 - }, - "per_page": { - "type": "integer", - "example": 15 - }, - "current_page": { - "type": "integer", - "example": 1 - }, - "last_page": { - "type": "integer", - "example": 1 - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/preferred-locations/{location}/areas": { - "get": { - "tags": [ - "General" - ], - "summary": "Get Localized Areas for a Preferred Location (Path Parameter)", - "description": "Returns a list of localized neighborhoods or sub-locations mapped under a specific preferred location (Dubai, Abu Dhabi, Oman) using path routing.", - "security": [], - "parameters": [ - { - "name": "location", - "in": "path", - "required": true, - "description": "The preferred location name (e.g. dubai, abu dhabi, oman).", - "schema": { - "type": "string" - } - }, - { - "name": "lang", - "in": "query", - "schema": { - "type": "string", - "default": "en" - }, - "description": "Language locale code (e.g. 'en', 'hindi', 'sawahi', 'taglo', 'tamil')" - }, - { - "name": "Accept-Language", - "in": "header", - "schema": { - "type": "string" - }, - "description": "Fallback header locale (e.g. 'en', 'hi', 'sw', 'tl', 'ta')" - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Search keyword to filter areas by name or code" - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "Page number for pagination" - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 15 - }, - "description": "Number of areas to return per page" - } - ], - "responses": { - "200": { - "description": "Areas retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "location": { - "type": "string", - "example": "dubai" - }, - "areas": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "marina" - }, - "name": { - "type": "string", - "example": "Dubai Marina" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 17 - }, - "per_page": { - "type": "integer", - "example": 15 - }, - "current_page": { - "type": "integer", - "example": 1 - }, - "last_page": { - "type": "integer", - "example": 2 - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/preferred-locations/areas": { - "get": { - "tags": [ - "General" - ], - "summary": "Get Localized Areas for a Preferred Location (Query Parameter)", - "description": "Returns a list of localized neighborhoods or sub-locations mapped under a specific preferred location (Dubai, Abu Dhabi, Oman) using query parameter routing.", - "security": [], - "parameters": [ - { - "name": "city", - "in": "query", - "required": false, - "description": "The preferred location name (e.g. dubai, abu dhabi, oman).", - "schema": { - "type": "string" - } - }, - { - "name": "location", - "in": "query", - "required": false, - "description": "Alternative key for location name (e.g. dubai, abu dhabi, oman).", - "schema": { - "type": "string" - } - }, - { - "name": "lang", - "in": "query", - "schema": { - "type": "string", - "default": "en" - }, - "description": "Language locale code (e.g. 'en', 'hindi', 'sawahi', 'taglo', 'tamil')" - }, - { - "name": "Accept-Language", - "in": "header", - "schema": { - "type": "string" - }, - "description": "Fallback header locale (e.g. 'en', 'hi', 'sw', 'tl', 'ta')" - }, - { - "name": "search", - "in": "query", - "schema": { - "type": "string" - }, - "description": "Search keyword to filter areas by name or code" - }, - { - "name": "page", - "in": "query", - "schema": { - "type": "integer", - "default": 1 - }, - "description": "Page number for pagination" - }, - { - "name": "per_page", - "in": "query", - "schema": { - "type": "integer", - "default": 15 - }, - "description": "Number of areas to return per page" - } - ], - "responses": { - "200": { - "description": "Areas retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "location": { - "type": "string", - "example": "dubai" - }, - "areas": { - "type": "array", - "items": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "marina" - }, - "name": { - "type": "string", - "example": "Dubai Marina" - } - } - } - }, - "pagination": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "example": 17 - }, - "per_page": { - "type": "integer", - "example": 15 - }, - "current_page": { - "type": "integer", - "example": 1 - }, - "last_page": { - "type": "integer", - "example": 2 - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/workers/send-otp": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Send Mobile OTP Verification Code", - "description": "Dispatches a mock 6-digit OTP verification code ('111111') to the worker's mobile phone number or email address.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone" - ], - "properties": { - "phone": { - "type": "string", - "example": "+971501234567", - "description": "Mobile contact phone number or identifier." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OTP dispatched successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Verification code sent. (Dev mock: 111111)" - }, - "identifier": { - "type": "string", - "example": "+971501234567" - } - } - } - } - } - } - } - } - }, - "/workers/verify-otp": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Verify Mobile OTP Code", - "description": "Validates the received 6-digit OTP code against the cached record. Sets a 1-hour secure gate allowing profile setup.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone", - "otp" - ], - "properties": { - "phone": { - "type": "string", - "example": "+971501234567" - }, - "otp": { - "type": "string", - "example": "111111", - "description": "6-digit OTP received by the worker." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OTP verified and registration gate unlocked.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "OTP verified successfully. You may proceed with profile setup." - }, - "identifier": { - "type": "string", - "example": "+971501234567" - } - } - } - } - } - }, - "422": { - "description": "Invalid OTP code or expired session." - } - } - } - }, - "/workers/setup-profile": { - "post": { - "tags": [ - "Worker/Auth" - ], - "summary": "Complete Basic Profile Setup", - "description": "Saves basic onboarding data (Name, Nationality, Language, Skills) and generates the permanent worker account along with a secure bearer access token.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "phone", - "name", - "nationality", - "language" - ], - "properties": { - "phone": { - "type": "string", - "example": "+971501234567" - }, - "name": { - "type": "string", - "example": "Rahul Sharma" - }, - "nationality": { - "type": "string", - "example": "Indian" - }, - "language": { - "type": "string", - "enum": [ - "HI", - "SW", - "TL", - "TA" - ], - "example": "HI" - }, - "skills": { - "type": "array", - "items": { - "type": "integer" - }, - "example": [ - 6, - 8 - ], - "description": "Array of skill IDs" - }, - "gender": { - "type": "string", - "enum": [ - "male", - "female", - "other" - ], - "example": "male", - "description": "Gender of the worker (male, female, other)." - }, - "live_in_out": { - "type": "string", - "enum": [ - "live_in", - "live_out" - ], - "example": "live_out", - "description": "Accommodation preference (live_in, live_out)." - }, - "preferred_location": { - "type": "string", - "example": "Dubai Marina", - "description": "Preferred work location/area." - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Account created and profile configured successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Registration complete! Welcome to Migrant." - }, - "data": { - "type": "object", - "properties": { - "worker": { - "$ref": "#/components/schemas/Worker" - }, - "token": { - "type": "string", - "example": "abc123xyz456...secureToken..." - } - } - } - } - } - } - } - } - } - } - }, - "/workers/profile": { - "get": { - "tags": [ - "Worker/Profile" - ], - "summary": "Get Profile details", - "description": "Retrieves the authenticated worker's profile details including their selected job category, connected skills, and uploaded documents.", - "responses": { - "200": { - "description": "Worker profile details retrieved.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "worker": { - "$ref": "#/components/schemas/Worker" - } - } - } - } - } - } - } - } - } - } - }, - "/workers/profile/update": { - "post": { - "tags": [ - "Worker/Profile" - ], - "summary": "Update Profile details", - "description": "Allows workers to customize and complete their profiles by updating age, nationality, desired salary, bio, availability, experience, religion, categories, and master skills list.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Amina Al-Masry" - }, - "age": { - "type": "integer", - "example": 28 - }, - "nationality": { - "type": "string", - "example": "Egypt" - }, - "salary": { - "type": "number", - "example": 1800 - }, - "availability": { - "type": "string", - "example": "Immediate" - }, - "experience": { - "type": "string", - "example": "4 Years" - }, - "religion": { - "type": "string", - "example": "Muslim" - }, - "bio": { - "type": "string", - "example": "Highly certified housekeeper and babysitter with cooking experience." - }, - "skills": { - "type": "array", - "items": { - "type": "integer" - }, - "example": [ - 6, - 8 + "security": [ + { + "bearerAuth": [ + ] - }, - "fcm_token": { - "type": "string", - "example": "fcm-device-token-xyz-123", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Profile updated successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Profile updated successfully." - }, - "data": { - "type": "object", - "properties": { - "worker": { - "$ref": "#/components/schemas/Worker" - } - } - } - } - } - } - } - } - } - } - }, - "/workers/go-live": { - "post": { - "tags": [ - "Worker/Profile" - ], - "summary": "Profile Go Live", - "description": "Sets the worker status to 'active' and availability to 'Immediate', making the profile searchable on the sponsor/employer marketplace.", - "responses": { - "200": { - "description": "Worker profile is now live and searchable.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Your profile is now live! Status set to Active." - } - } - } - } - } - } - } - } - }, - "/workers/profile/toggle-availability": { - "post": { - "tags": [ - "Worker/Profile" - ], - "summary": "Toggle Profile Search Visibility", - "description": "Updates worker search visibility based on whether they are still actively seeking a job. 'still_looking' as true makes profile active, false hides it.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "still_looking" - ], - "properties": { - "still_looking": { - "type": "boolean", - "example": true, - "description": "Whether the worker is still actively looking for job offers." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Availability toggled successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "still_looking": { - "type": "boolean", - "example": true - }, - "status": { - "type": "string", - "example": "active" - } - } - } - } - } - } - } - } - }, - "/workers/profile/mark-hired": { - "post": { - "tags": [ - "Worker/Profile" - ], - "summary": "Mark Worker as Hired", - "description": "Changes the worker's status to 'Hired', which automatically removes them from active marketplace searches.", - "responses": { - "200": { - "description": "Worker marked as Hired.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Congratulations! You have been successfully marked as Hired." - } - } - } - } - } - } - } - } - }, - "/workers/offers": { - "get": { - "tags": [ - "Worker/Offers" - ], - "summary": "View Received Job Offers", - "description": "Returns a list of all custom job/hiring offers received by the worker from different employers (e.g. Work Date, location, salaries, and notes).", - "responses": { - "200": { - "description": "List of job offers retrieved.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "offers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/JobOffer" - } - } - } - } - } - } - } - } - } - } - } - }, - "/workers/offers/{id}/respond": { - "post": { - "tags": [ - "Worker/Offers" - ], - "summary": "Accept or Reject Job Offer", - "description": "Responds to a pending job offer. If accepted, the job offer status becomes 'accepted' and the worker's status is automatically changed to 'Hired', which reflects immediately in the Employer panel.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The Job Offer ID reference.", - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "string", - "enum": [ - "accepted", - "rejected" - ], - "example": "accepted", - "description": "Response action (accepted or rejected)." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Job offer response saved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Job offer successfully accepted." - }, - "data": { - "type": "object", - "properties": { - "offer": { - "$ref": "#/components/schemas/JobOffer" - }, - "worker_status": { - "type": "string", - "example": "Hired" - } - } - } - } - } - } - } - } - } - } - }, - "/workers/conversations": { - "get": { - "tags": [ - "Worker/Conversations" - ], - "summary": "Get Conversations List (Worker)", - "description": "Retrieves all active conversations for the authenticated worker.", - "responses": { - "200": { - "description": "Conversations list retrieved successfully." - } - } - } - }, - "/workers/conversations/{id}/messages": { - "get": { - "tags": [ - "Worker/Conversations" - ], - "summary": "Get Conversation Messages (Worker)", - "description": "Retrieves message history for a conversation and marks incoming employer messages as read.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Messages retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Worker/Conversations" - ], - "summary": "Send Reply Message (Worker)", - "description": "Sends a reply message from the worker to the employer.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "text": { - "type": "string", - "example": "Yes, I am available.", - "description": "Optional text message content (required if no file is uploaded)." - }, - "file": { - "type": "string", - "format": "binary", - "description": "Optional file or voice note attachment (Max 10MB). Supports images, documents, and common audio formats." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Message sent successfully." - } - } - } - }, - "/workers/report": { - "post": { - "tags": [ - "Worker/Profile" - ], - "summary": "Submit a Safety/Moderation Report (Worker)", - "description": "Submits a report on a chat conversation or a review. The report is logged in the safety queue and reviewed by administrators.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "type", - "item_id", - "reason" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "Chat", - "Review" - ], - "example": "Chat", - "description": "The type of entity being reported (Chat or Review)." - }, - "item_id": { - "type": "string", - "example": "1", - "description": "The ID of the conversation or review being reported." - }, - "user_id": { - "type": "string", - "example": "5", - "description": "Optional ID of the user being reported." - }, - "reason": { - "type": "string", - "example": "Others", - "description": "The reason for submitting this report." - }, - "others": { - "type": "string", - "example": "Spam and malicious links", - "description": "Custom reason string. Used only if reason is 'Others'." - }, - "description": { - "type": "string", - "example": "The other party sent highly inappropriate and insulting messages.", - "description": "Optional detailed explanation/comments." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Report submitted successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Report submitted successfully. Our safety team will review it shortly." - }, - "report_id": { - "type": "string", - "example": "REP-ABCD1234" - } - } - } - } - } - }, - "404": { - "description": "Conversation or Review not found, or user is not authorized to report it." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/workers/report-reasons": { - "get": { - "tags": [ - "Worker/Profile" - ], - "summary": "Get Safety Report Reasons (Worker)", - "description": "Returns a list of active safety/moderation report reasons. Can filter reasons by target type (Chat or Review).", - "parameters": [ - { - "name": "type", - "in": "query", - "required": false, - "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", - "schema": { - "type": "string", - "enum": [ - "Chat", - "Review" - ] - } - } - ], - "responses": { - "200": { - "description": "List of report reasons retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "reasons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "reason": { - "type": "string", - "example": "Abusive language / Profanity" - }, - "type": { - "type": "string", - "example": "Both" - } - } - } - } - } - } - } - } - } - } - } - }, - "/workers/tickets": { - "get": { - "tags": [ - "Worker/Support" - ], - "summary": "List Support Tickets (Worker)", - "description": "Retrieves a paginated list of all support tickets created by the authenticated worker.", - "responses": { - "200": { - "description": "Tickets retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Worker/Support" - ], - "summary": "Create Support Ticket (Worker)", - "description": "Creates a support ticket for a worker with a subject, description, and optional priority level.", - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "subject", - "description" - ], - "properties": { - "subject": { - "type": "string", - "example": "App login crashes on launch" - }, - "description": { - "type": "string", - "example": "When opening the app on my phone, it crashes immediately on the splash screen." - }, - "priority": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ], - "example": "medium" - }, - "reason_id": { - "type": "integer", - "example": 1, - "description": "Optional report/support reason ID." - }, - "voice_note": { - "type": "string", - "format": "binary", - "description": "Optional audio/voice note file attachment." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Ticket created successfully." - } - } - } - }, - "/workers/tickets/{id}": { - "get": { - "tags": [ - "Worker/Support" - ], - "summary": "Get Support Ticket Details & Replies (Worker)", - "description": "Retrieves the details of a support ticket and its chronological reply history.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Ticket details and replies retrieved successfully." - } - } - } - }, - "/workers/tickets/{id}/reply": { - "post": { - "tags": [ - "Worker/Support" - ], - "summary": "Reply to Support Ticket (Worker)", - "description": "Submits a text reply or message to an active, open, or resolved support ticket.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "I tried clearing cache and it still crashes." - }, - "voice_note": { - "type": "string", - "format": "binary", - "description": "Optional audio/voice note file attachment." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Reply posted successfully." - } - } - } - }, - "/employers/register": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Register Sponsor (Alias)", - "description": "Legacy route matching the simplified sponsor registration (Name, Email, Phone) (Step 1).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "email", - "phone" - ], - "properties": { - "name": { - "type": "string", - "example": "Ahmad Bin Ahmed", - "description": "Sponsor's full name" - }, - "email": { - "type": "string", - "example": "ahmad@example.com", - "description": "Sponsor's contact email address" - }, - "phone": { - "type": "string", - "example": "+971509990001", - "description": "Sponsor's mobile phone number" - }, - "emirates_id": { - "type": "object", - "description": "Optional JSON object containing extracted Emirates ID details.", - "properties": { - "emirates_id_number": { - "type": "string", - "example": "784-1988-5310327-2" - }, - "name": { - "type": "string", - "example": "Ahmad Bin Ahmed" - }, - "date_of_birth": { - "type": "string", - "example": "1990-01-01" - }, - "nationality": { - "type": "string", - "example": "Emirati" - }, - "issue_date": { - "type": "string", - "example": "2023-04-11" - }, - "expiry_date": { - "type": "string", - "example": "2028-04-11" - }, - "employer": { - "type": "string", - "example": "Federal Authority" - }, - "issue_place": { - "type": "string", - "example": "Abu Dhabi" - }, - "occupation": { - "type": "string", - "example": "Manager" - } - } - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - }, - "address": { - "type": "string", - "example": "Villa 45, Street 12", - "description": "Physical address/location details." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Sponsor registered successfully." - } - } - } - }, - "/employers/verify": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Verify Sponsor Email OTP", - "description": "Verifies the email address with the OTP verification code (Step 2).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "otp" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "otp": { - "type": "string", - "example": "111111", - "description": "6-digit OTP code" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Email verified successfully." - } - } - } - }, - "/employers/payment": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Sponsor Subscription Payment", - "description": "Submits a successful plan selection and PayTabs payment confirmation (Step 4).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "plan_id", - "amount_aed", - "paytabs_transaction_id" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "plan_id": { - "type": "string", - "example": "premium" - }, - "amount_aed": { - "type": "number", - "example": 199 - }, - "paytabs_transaction_id": { - "type": "string", - "example": "TXN998877" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Subscription payment confirmed." - } - } - } - }, - "/employers/password": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Create Sponsor Account Password", - "description": "Configures and finalizes the portal login password to complete registration (Step 5). Returns bearer token.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "password", - "password_confirmation" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "password": { - "type": "string", - "format": "password", - "example": "Password@123" - }, - "password_confirmation": { - "type": "string", - "format": "password", - "example": "Password@123" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Password set and registration finalized successfully." - } - } - } - }, - "/employers/plans": { - "get": { - "tags": [ - "Employer/Auth" - ], - "summary": "Get Available Subscription Plans", - "description": "Returns details (price, features) of subscription packages available to sponsors.", - "security": [], - "responses": { - "200": { - "description": "Subscription plans list retrieved successfully." - } - } - } - }, - "/employers/login": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Authenticate Employer or Sponsor", - "description": "Unified authentication endpoint. Validates email/mobile and password, then returns a stateless Bearer token and the user's role (employer or sponsor).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "password" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com", - "description": "Email address (required if mobile is not provided)" - }, - "mobile": { - "type": "string", - "example": "+971509990001", - "description": "Mobile number (required if email is not provided)" - }, - "password": { - "type": "string", - "example": "Password@123" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Login successful. Returns Bearer token and role.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Login successful." - }, - "role": { - "type": "string", - "example": "employer", - "enum": [ - "employer", - "sponsor" - ] - }, - "data": { - "type": "object", - "properties": { - "token": { - "type": "string", - "example": "api_token_here" - } - } - } - } - } - } - } - } - } - } - }, - "/employers/forgot-password": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Request Password Reset Code", - "description": "Validates employer email and sends a 6-digit password reset verification code (OTP).", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Password reset verification code sent successfully." - }, - "404": { - "description": "No employer account found with this email." - } - } - } - }, - "/employers/reset-password": { - "post": { - "tags": [ - "Employer/Auth" - ], - "summary": "Reset Password with Verification Code", - "description": "Verifies the 6-digit OTP code and updates the password for both employer user and sponsor records.", - "security": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "email", - "otp", - "password", - "password_confirmation" - ], - "properties": { - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "otp": { - "type": "string", - "example": "111111" - }, - "password": { - "type": "string", - "example": "SecureNewPassword@123" - }, - "password_confirmation": { - "type": "string", - "example": "SecureNewPassword@123" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Password reset successfully." - }, - "400": { - "description": "Invalid or expired verification code." - } - } - } - }, - "/employers/conversations": { - "get": { - "tags": [ - "Employer/Conversations" - ], - "summary": "Get Conversations List (Employer)", - "description": "Retrieves all active candidate conversations for the authenticated employer.", - "responses": { - "200": { - "description": "Conversations list retrieved successfully." - } - } - } - }, - "/employers/conversations/{id}/messages": { - "get": { - "tags": [ - "Employer/Conversations" - ], - "summary": "Get Conversation Messages (Employer)", - "description": "Retrieves message history for a conversation and marks incoming worker messages as read.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Messages retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Employer/Conversations" - ], - "summary": "Send Reply Message (Employer)", - "description": "Sends a reply message from the employer to the worker.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "text": { - "type": "string", - "example": "When can you start?", - "description": "Optional text message content (required if no file is uploaded)." - }, - "file": { - "type": "string", - "format": "binary", - "description": "Optional file or voice note attachment (Max 10MB). Supports images, documents, and common audio formats." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Message sent successfully." - } - } - } - }, - "/employers/conversations/start": { - "post": { - "tags": [ - "Employer/Conversations" - ], - "summary": "Start Conversation with Worker", - "description": "Starts or retrieves a conversation with the specified worker ID.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "worker_id" - ], - "properties": { - "worker_id": { - "type": "integer", - "example": 1 - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Conversation started successfully." - } - } - } - }, - "/workers/announcements": { - "get": { - "tags": [ - "Worker/CharityEvents" - ], - "summary": "Get Charity Events (Worker)", - "description": "Allows workers to retrieve the list of active charity events, food drives, and medical support programs scheduled in Dubai.", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "List of charity events retrieved successfully." - } - } - } - }, - "/workers/announcements/new": { - "get": { - "tags": [ - "Worker/CharityEvents" - ], - "summary": "Get New/Unviewed Announcements/Charity Events", - "description": "Retrieves up to 3 active approved charity events/announcements that the worker has not yet viewed, and automatically marks them as viewed.", - "responses": { - "200": { - "description": "Successfully retrieved new announcements.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 4 - }, - "title": { - "type": "string", - "example": "Free Dental Checkup Camp" - }, - "body": { - "type": "string", - "example": "Emirates Charity is providing free screening." - }, - "type": { - "type": "string", - "example": "charity" - }, - "employer_name": { - "type": "string", - "example": "Emirates Charity" - }, - "company_name": { - "type": "string", - "example": "Emirates Charity Foundation" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2026-06-01T10:00:00.000000Z" - }, - "time_ago": { - "type": "string", - "example": "5 hours ago" - } - } - } - } - } - } - } - } - }, - "401": { - "description": "Unauthenticated." - } - } - } - }, - "/workers/announcements/{id}/view": { - "post": { - "tags": [ - "Worker/CharityEvents" - ], - "summary": "Mark Specific Announcement as Viewed", - "description": "Explicitly marks a specific announcement/charity event as viewed by the authenticated worker.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Announcement ID", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Announcement marked as viewed successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Announcement marked as viewed." - } - } - } - } - } - }, - "401": { - "description": "Unauthenticated." - }, - "404": { - "description": "Announcement not found." - } - } - } - }, - "/employers/announcements": { - "get": { - "tags": [ - "Employer/CharityEvents" - ], - "summary": "Get Posted Charity Events (Employer)", - "description": "Allows employers/sponsors to retrieve the list of charity events they have published.", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "List of posted charity events retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Employer/CharityEvents" - ], - "summary": "Publish Charity Event (Employer)", - "description": "Allows employers/sponsors to publish a new community charity event or drive to all workers in Dubai, triggering instant push notifications and morning-of reminders.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "title", - "content", - "provided_items", - "event_date", - "event_time", - "location_details", - "location_pin" - ], - "properties": { - "title": { - "type": "string", - "example": "Free Dental Checkup Camp" - }, - "content": { - "type": "string", - "example": "Emirates Charity is providing free professional dental screening, cleanings, and educational packages for domestic helpers." - }, - "provided_items": { - "type": "string", - "example": "Free Dental screening, cleanings, and wellness kits" - }, - "event_date": { - "type": "string", - "format": "date", - "example": "2026-06-15" - }, - "event_time": { - "type": "string", - "example": "9:00 AM - 4:00 PM" - }, - "location_details": { - "type": "string", - "example": "Al Quoz Community Hall, Dubai" - }, - "location_pin": { - "type": "string", - "format": "uri", - "example": "https://maps.app.goo.gl/xyz" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Charity event created and notifications scheduled successfully." - } - } - } - }, - "/employers/announcements/{id}": { - "delete": { - "tags": [ - "Employer/CharityEvents" - ], - "summary": "Delete Charity Event (Employer)", - "description": "Allows employers/sponsors to delete a previously published charity event.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Charity event deleted successfully." - } - } - } - }, - "/employers/dashboard": { - "get": { - "tags": [ - "Employer/Profile" - ], - "summary": "Get Employer Dashboard stats & recent events", - "description": "Retrieves stats including contacted workers count, total hired workers, saved candidates, current plan details, and recent announcements or events.", - "responses": { - "200": { - "description": "Employer dashboard stats retrieved successfully." - } - } - } - }, - "/employers/profile": { - "get": { - "tags": [ - "Employer/Profile" - ], - "summary": "Get Profile details (Employer)", - "description": "Retrieves the authenticated employer's profile details including their selected company name, phone, language preference, and notification settings.", - "responses": { - "200": { - "description": "Employer profile details retrieved successfully." - } - } - } - }, - "/employers/profile/update": { - "post": { - "tags": [ - "Employer/Profile" - ], - "summary": "Update Profile details (Employer)", - "description": "Allows employers to update their profile details (company name, phone number, name, email, language preference, and notification settings) and change their account password.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "email", - "phone", - "company_name", - "language", - "notifications" - ], - "properties": { - "name": { - "type": "string", - "example": "Ahmad" - }, - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "phone": { - "type": "string", - "example": "+971509990001" - }, - "company_name": { - "type": "string", - "example": "Ahmad Tech Ltd" - }, - "language": { - "type": "string", - "enum": [ - "English", - "Arabic", - "english", - "arabic" - ], - "example": "English" - }, - "notifications": { - "type": "boolean", - "example": true - }, - "current_password": { - "type": "string", - "example": "Password@123" - }, - "new_password": { - "type": "string", - "example": "NewSecurePassword@123" - }, - "new_password_confirmation": { - "type": "string", - "example": "NewSecurePassword@123" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example", - "description": "Firebase Cloud Messaging token for push notifications." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Profile updated successfully." - } - } - } - }, - "/employers/workers": { - "get": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Get Available Workers", - "description": "Returns a list of verified workers that are currently available to be hired.", - "parameters": [ - { - "name": "search", - "in": "query", - "required": false, - "description": "Search term matching helper name, nationality, or religion.", - "schema": { - "type": "string" - } - }, - { - "name": "skills", - "in": "query", - "required": false, - "description": "Filter by skills (comma-separated list).", - "schema": { - "type": "string" - } - }, - { - "name": "language", - "in": "query", - "required": false, - "description": "Filter by language.", - "schema": { - "type": "string" - } - }, - { - "name": "nationality", - "in": "query", - "required": false, - "description": "Filter by nationality name (comma-separated list for multiple choice, e.g. Indian,Kenyan,Ethiopian).", - "schema": { - "type": "string" - } - }, - { - "name": "gender", - "in": "query", - "required": false, - "description": "Filter by gender (e.g. male, female).", - "schema": { - "type": "string" - } - }, - { - "name": "preferred_location", - "in": "query", - "required": false, - "description": "Filter by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).", - "schema": { - "type": "string" - } - }, - { - "name": "area", - "in": "query", - "required": false, - "description": "Filter by preferred area (comma-separated list, e.g. Marina,Al Barsha).", - "schema": { - "type": "string" - } - }, - { - "name": "job_type", - "in": "query", - "required": false, - "description": "Filter by job type (comma-separated list, e.g. full-time,part-time).", - "schema": { - "type": "string" - } - }, - { - "name": "accommodation_type", - "in": "query", - "required": false, - "description": "Filter by accommodation type (comma-separated list, e.g. live_in,live_out).", - "schema": { - "type": "string" - } - }, - { - "name": "in_country", - "in": "query", - "required": false, - "description": "Filter by in country status (true/false or yes/no or in/out).", - "schema": { - "type": "string" - } - }, - { - "name": "out_country", - "in": "query", - "required": false, - "description": "Filter by out country status (true/false or yes/no or out/in).", - "schema": { - "type": "string" - } - }, - { - "name": "visa_status", - "in": "query", - "required": false, - "description": "Filter by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.", - "schema": { - "type": "string" - } - }, - { - "name": "availability", - "in": "query", - "required": false, - "description": "Filter by availability status.", - "schema": { - "type": "string" - } - }, - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "Available workers list retrieved successfully." - } - } - } - }, - "/employers/workers/{id}": { - "get": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Get Worker Profile Detail", - "description": "Retrieves the complete profile details for a worker, mirroring the web detailed view, along with active conversation details if already started with this employer.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Worker ID reference.", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Worker profile details retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "worker": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "name": { - "type": "string", - "example": "Jane Doe" - }, - "nationality": { - "type": "string", - "example": "Filipino" - }, - "photo": { - "type": "string", - "example": "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200" - }, - "emirates_id_status": { - "type": "string", - "example": "Passport Verified", - "description": "Deprecated - Use passport_status instead" - }, - "passport_status": { - "type": "string", - "example": "Passport Verified" - }, - "category": { - "type": "string", - "example": "Domestic Worker" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "childcare", - "cooking" - ] - }, - "availability_status": { - "type": "string", - "example": "Active" - }, - "visa_status": { - "type": "string", - "example": "Residence Visa" - }, - "experience": { - "type": "string", - "example": "5 Years" - }, - "experience_years": { - "type": "integer", - "example": 5 - }, - "salary": { - "type": "integer", - "example": 2500 - }, - "religion": { - "type": "string", - "example": "Christian" - }, - "languages": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "English", - "Tagalog" - ] - }, - "age": { - "type": "integer", - "example": 28 - }, - "verified": { - "type": "boolean", - "example": true - }, - "preferred_job_type": { - "type": "string", - "example": "live-in" - }, - "bio": { - "type": "string", - "example": "Experienced and caring domestic worker specialing in childcare and housekeeping..." - }, - "rating": { - "type": "number", - "example": 4.3 - }, - "reviews_count": { - "type": "integer", - "example": 6 - }, - "reviews": { - "type": "array", - "items": { - "type": "object" + } + ], + "paths": { + "/sponsors/register": { + "post": { + "tags": [ + "Sponsor/Auth" + ], + "summary": "Register Sponsor Account", + "description": "Registers a new sponsor with basic information and uploads their organization/trade license. Sponsors have restricted access — dashboard and charity events only. No payment required.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "full_name", + "mobile", + "password", + "organization_name", + "email", + "nationality", + "city", + "address", + "country_code" + ], + "properties": { + "full_name": { + "type": "string", + "example": "Mohammed Al-Rashidi", + "description": "Full legal name of the sponsor. (REQUIRED)" + }, + "mobile": { + "type": "string", + "example": "+971501112233", + "description": "Mobile phone number — must be unique. (REQUIRED)" + }, + "password": { + "type": "string", + "format": "password", + "example": "securepass123", + "description": "Login password (min 6 characters). (REQUIRED)" + }, + "license": { + "type": "object", + "description": "Optional JSON object containing extracted organization/trade license details.", + "properties": { + "license_number": { + "type": "string", + "example": "123456" + }, + "organization_name": { + "type": "string", + "example": "Al-Rashidi Charitable Foundation" + }, + "expiry_date": { + "type": "string", + "example": "2028-06-12" + } + } + }, + "emirates_id": { + "type": "object", + "description": "Optional JSON object containing extracted Emirates ID details.", + "properties": { + "emirates_id_number": { + "type": "string", + "example": "784-1988-5310327-2" + }, + "name": { + "type": "string", + "example": "KRISHNA PRASAD" + }, + "nationality": { + "type": "string", + "example": "NPL" + }, + "date_of_birth": { + "type": "string", + "example": "1988-03-22" + }, + "expiry_date": { + "type": "string", + "example": "2028-04-11" + }, + "issue_date": { + "type": "string", + "example": "2023-04-11" + }, + "employer": { + "type": "string", + "example": "Federal Authority" + }, + "issue_place": { + "type": "string", + "example": "Abu Dhabi" + }, + "occupation": { + "type": "string", + "example": "Manager" + } + } + }, + "organization_name": { + "type": "string", + "example": "Al-Rashidi Charitable Foundation", + "description": "Name of the sponsoring organization. (REQUIRED)" + }, + "email": { + "type": "string", + "format": "email", + "example": "info@alrashidi.ae", + "description": "Email address (must be unique). (REQUIRED)" + }, + "nationality": { + "type": "string", + "example": "UAE", + "description": "Nationality of the sponsor. (REQUIRED)" + }, + "city": { + "type": "string", + "example": "Dubai", + "description": "City of residence. (REQUIRED)" + }, + "address": { + "type": "string", + "example": "Villa 12, Jumeirah 2", + "description": "Physical address. (REQUIRED)" + }, + "country_code": { + "type": "string", + "example": "+971", + "description": "Country dial code. (REQUIRED)" + }, + "license_expiry": { + "type": "string", + "format": "date", + "example": "2028-06-12", + "description": "Expiry date of the license (YYYY-MM-DD). (REQUIRED)" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Sponsor registered successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Sponsor account registered successfully. Your license is pending admin review." + }, + "data": { + "type": "object", + "properties": { + "sponsor": { + "$ref": "#/components/schemas/Sponsor" + }, + "token": { + "type": "string", + "example": "abc123...bearerToken..." + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error (duplicate mobile/email, missing required fields)." + } + } + } + }, + "/sponsors/register/license": { + "post": { + "tags": [ + "Sponsor/Auth" + ], + "summary": "Upload Sponsor Trade License", + "description": "Uploads and processes the trade license document of a Sponsor using OCR.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "email", + "license_file" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "info@alrashidi.ae", + "description": "Email address of the registered sponsor. (REQUIRED)" + }, + "license_file": { + "type": "string", + "format": "binary", + "description": "Trade license image/PDF file. (REQUIRED)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Trade license processed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "License uploaded and processed successfully." + }, + "ocr_extracted_data": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "expiry_date": { + "type": "string", + "example": "2028-06-12" + }, + "license_number": { + "type": "string", + "example": "123456" + }, + "organization_name": { + "type": "string", + "example": "Charity Co" + } + } + }, + "email": { + "type": "string", + "example": "info@alrashidi.ae" + } + } + } + } + } + }, + "404": { + "description": "Sponsor account not found." + }, + "422": { + "description": "Validation error." + } + } + } + }, + "/sponsors/login": { + "post": { + "tags": [ + "Sponsor/Auth" + ], + "summary": "Authenticate Sponsor or Employer", + "description": "Unified authentication endpoint. Validates email/mobile and password, then returns a stateless Bearer token and the user\u0027s role (employer or sponsor). Same behavior as /employers/login.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "password" + ], + "properties": { + "email": { + "type": "string", + "example": "ahmad@example.com", + "description": "Email address (required if mobile is not provided)" + }, + "mobile": { + "type": "string", + "example": "+971509990001", + "description": "Mobile number (required if email is not provided)" + }, + "password": { + "type": "string", + "example": "Password@123" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Login successful. Returns Bearer token and role.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Login successful." + }, + "role": { + "type": "string", + "example": "sponsor", + "enum": [ + "employer", + "sponsor" + ] + }, + "data": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "api_token_here" + } + } + } + } + } + } + } + } + } + } + }, + "/sponsors/dashboard": { + "get": { + "tags": [ + "Sponsor" + ], + "summary": "Sponsor Dashboard", + "description": "Returns the sponsor profile summary, latest charity events, and total \u0026 active counts for employers and workers.", + "responses": { + "200": { + "description": "Dashboard data retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "type": "object", + "properties": { + "sponsor": { + "type": "object" + }, + "recent_charity_events": { + "type": "array", + "items": { + "type": "object" + } + }, + "total_events": { + "type": "integer" + }, + "employer_stats": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 12 + }, + "active": { + "type": "integer", + "example": 8 + } + } + }, + "worker_stats": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 150 + }, + "active": { + "type": "integer", + "example": 120 + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthenticated." + } + } + } + }, + "/sponsors/charity-events": { + "get": { + "tags": [ + "Sponsor" + ], + "summary": "List Charity Events", + "description": "Returns a paginated list of all charity and announcement events. Optionally filter by type.", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 15 + } + }, + { + "name": "type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Optional event type filter (e.g. \u0027charity\u0027, \u0027update\u0027)." + } + ], + "responses": { + "200": { + "description": "Events retrieved successfully." + }, + "401": { + "description": "Unauthenticated." + } + } + }, + "post": { + "tags": [ + "Sponsor" + ], + "summary": "Post Charity Event (Sponsor)", + "description": "Creates and publishes a new charity event or community drive for the authenticated sponsor.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "body", + "event_date", + "start_time", + "end_time", + "provided_items", + "location_details", + "location_pin" + ], + "properties": { + "title": { + "type": "string", + "example": "Free Dental Checkup Drive" + }, + "body": { + "type": "string", + "example": "Emirates Charity is organizing a free dental checkup for all workers this Friday morning." + }, + "type": { + "type": "string", + "enum": [ + "charity", + "info", + "warning", + "success" + ], + "example": "charity" + }, + "event_date": { + "type": "string", + "format": "date", + "example": "2026-06-15" + }, + "start_time": { + "type": "string", + "example": "09:00 AM" + }, + "end_time": { + "type": "string", + "example": "04:00 PM" + }, + "provided_items": { + "type": "string", + "example": "Free Dental screening, cleanings, and wellness kits" + }, + "location_details": { + "type": "string", + "example": "Al Quoz Community Hall, Dubai" + }, + "location_pin": { + "type": "string", + "format": "uri", + "example": "https://maps.app.goo.gl/xyz" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Charity event posted successfully." + }, + "401": { + "description": "Unauthenticated." + }, + "422": { + "description": "Validation error." + } + } } - }, - "similar_workers": { - "type": "array", - "items": { - "type": "object" + }, + "/sponsors/profile": { + "get": { + "tags": [ + "Sponsor" + ], + "summary": "Get Sponsor Profile", + "description": "Returns the full profile of the authenticated sponsor.", + "responses": { + "200": { + "description": "Profile retrieved successfully." + }, + "401": { + "description": "Unauthenticated." + } + } + } + }, + "/workers/register": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Register Worker Account (Step 1)", + "description": "Creates the worker account and returns a secure Bearer token.\n\nDocument upload is a **separate step** after registration:\n- `POST /workers/register/passport` — upload passport (OCR-processed)\n- `POST /workers/register/visa` — upload visa (OCR-processed)\n\nBoth document endpoints require the Bearer token returned here.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "name", + "phone", + "salary", + "password" + ], + "properties": { + "name": { + "type": "string", + "example": "Amina Al-Masry", + "description": "Full legal name of the worker. (REQUIRED)" + }, + "phone": { + "type": "string", + "example": "+971509998888", + "description": "Contact mobile phone number (must be unique). (REQUIRED)" + }, + "salary": { + "type": "number", + "format": "float", + "example": 1500, + "description": "Desired minimum monthly salary in AED. (REQUIRED)" + }, + "password": { + "type": "string", + "format": "password", + "example": "securepassword123", + "description": "Secret password for subsequent mobile logins (min 6 chars). (REQUIRED)" + }, + "skills": { + "type": "array", + "items": { + "type": "integer" + }, + "example": [ + 1, + 3 + ], + "description": "Optional array of Skill IDs (can be sent as comma-separated string e.g. \u00271,3\u0027 or json \u0027[1,3]\u0027 in multipart)." + }, + "language": { + "type": "string", + "example": "English, Arabic", + "description": "Languages spoken by the worker (e.g. \u0027English, Arabic, Hindi\u0027)." + }, + "nationality": { + "type": "string", + "example": "Egypt", + "description": "Nationality of the worker." + }, + "age": { + "type": "integer", + "example": 28, + "description": "Age of the worker." + }, + "experience": { + "type": "string", + "example": "4 Years", + "description": "Years of experience." + }, + "in_country": { + "type": "boolean", + "example": true, + "description": "Whether the worker is currently in-country (true) or out-country (false)." + }, + "visa_status": { + "type": "string", + "example": "Tourist Visa", + "description": "Visa status (only required/applicable if in_country is true)." + }, + "preferred_job_type": { + "type": "string", + "example": "full-time", + "description": "Preferred job type." + }, + "live_in_out": { + "type": "string", + "enum": [ + "live_in", + "live_out" + ], + "example": "live_out", + "description": "Accommodation preference (live_in, live_out)." + }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male", + "description": "Gender of the worker (male, female, other)." + }, + "preferred_location": { + "type": "string", + "example": "Dubai Marina", + "description": "Preferred work location/area." + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + }, + "passport": { + "type": "object", + "description": "Optional JSON object containing extracted passport details.", + "properties": { + "authority": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "example": "14/05/1990" + }, + "date_of_expiry": { + "type": "string", + "example": "05/07/2022" + }, + "date_of_issue": { + "type": "string", + "example": "05/07/2017" + }, + "document_type": { + "type": "string" + }, + "given_names": { + "type": "string" + }, + "issuing_country": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "passport_number": { + "type": "string" + }, + "personal_number": { + "type": "string" + }, + "place_of_birth": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "surname": { + "type": "string" + } + } + }, + "visa": { + "type": "object", + "description": "Optional JSON object containing extracted visa details.", + "properties": { + "accompanied_by": { + "type": "string" + }, + "expiry_date": { + "type": "string", + "example": "2024-02-15" + }, + "file_number": { + "type": "string" + }, + "id_number": { + "type": "string" + }, + "issue_date": { + "type": "string", + "example": "2022-02-16" + }, + "name": { + "type": "string" + }, + "passport_number": { + "type": "string" + }, + "place_of_issue": { + "type": "string" + }, + "profession": { + "type": "string" + }, + "sponsor": { + "type": "string" + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Worker registered successfully. Use the returned token for document upload steps.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Worker registered successfully. Please upload your passport and visa documents." + }, + "data": { + "type": "object", + "properties": { + "worker": { + "$ref": "#/components/schemas/Worker" + }, + "token": { + "type": "string", + "example": "abc123xyz456...secureToken..." + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error — field validation failed or Passport OCR extraction failed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Validation error." + }, + "errors": { + "type": "object", + "properties": { + "phone": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "This mobile number is already registered." + ] + }, + "name": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "The name field is required." + ] + } + } + } + } + } + } + } + }, + "500": { + "description": "Server error during registration (e.g. database transaction failure).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "An error occurred during worker registration. Please try again." + }, + "error": { + "type": "string", + "example": "Internal Server Error" + } + } + } + } + } + } + } + } + }, + "/workers/login": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Authenticate Worker", + "description": "Validates worker credentials (mobile phone number and password) and generates a secure, stateless Bearer token for api access.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone", + "password" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971509998888", + "description": "Registered mobile phone number." + }, + "password": { + "type": "string", + "format": "password", + "example": "securepassword123", + "description": "Secret account password." + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Worker logged in successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Worker logged in successfully." + }, + "data": { + "type": "object", + "properties": { + "worker": { + "$ref": "#/components/schemas/Worker" + }, + "token": { + "type": "string", + "example": "abc123xyz456...secureToken..." + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized access.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Invalid mobile number or password." + } + } + } + } + } + } + } + } + }, + "/workers/ocr/passport-vision": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Extract Passport Data using Google Cloud Vision API", + "description": "Upload a passport image file to extract OCR data using the Google Cloud Vision API.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "passport_file" + ], + "properties": { + "passport_file": { + "type": "string", + "format": "binary", + "description": "Passport image file (jpg/png/jpeg, max 10MB)." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Passport data successfully extracted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "passport_number": { + "type": "string", + "example": "Z43R34255" + }, + "given_names": { + "type": "string", + "example": "AHMAD" + }, + "date_of_birth": { + "type": "string", + "example": "03/07/1978" + }, + "nationality": { + "type": "string", + "example": "ARE" + }, + "issuing_country": { + "type": "string", + "example": "ARE" + }, + "place_of_birth": { + "type": "string", + "example": "DUBAI" + }, + "date_of_issue": { + "type": "string", + "example": "10/02/2015" + }, + "date_of_expiry": { + "type": "string", + "example": "10/02/2020" + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error (missing passport_file)." + }, + "400": { + "description": "OCR processing error or failed Google Vision API request." + } + } + } + }, + "/workers/config": { + "get": { + "tags": [ + "Worker/Auth" + ], + "summary": "Get Supported Languages and Config", + "description": "Returns a list of supported regional languages (Hindi, Swahili, Tagalog, Tamil) for helper onboarding.", + "security": [ + + ], + "responses": { + "200": { + "description": "Config and supported languages retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "languages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "HI" + }, + "name": { + "type": "string", + "example": "Hindi (हिन्दी)" + } + } + } + } + } + } + } + } + } + } + } + }, + "/workers/skills": { + "get": { + "tags": [ + "Worker/Auth" + ], + "summary": "Get Master Skills List", + "description": "Returns a list of all available master skills for helper onboarding profile setup.", + "security": [ + + ], + "responses": { + "200": { + "description": "Master skills list retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "skills": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "Cooking" + } + } + } + } + } + } + } + } + } + } + } + }, + "/nationalities": { + "get": { + "tags": [ + "General" + ], + "summary": "Get Nationalities List", + "description": "Returns a list of supported nationalities translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", + "security": [ + + ], + "parameters": [ + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + }, + "description": "Language locale code (e.g. \u0027en\u0027, \u0027hindi\u0027, \u0027sawahi\u0027, \u0027taglo\u0027, \u0027tamil\u0027)" + }, + { + "name": "Accept-Language", + "in": "header", + "schema": { + "type": "string" + }, + "description": "Fallback header locale (e.g. \u0027en\u0027, \u0027hi\u0027, \u0027sw\u0027, \u0027tl\u0027, \u0027ta\u0027)" + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search keyword to filter nationalities by name or code" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 500 + }, + "description": "Number of nationalities to return per page. Defaults to 500 on the backend to retrieve all nationalities by default." + } + ], + "responses": { + "200": { + "description": "Nationalities list retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "nationalities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "IN" + }, + "name": { + "type": "string", + "example": "Indian" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 185 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 13 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/languages": { + "get": { + "tags": [ + "General" + ], + "summary": "Get Languages List", + "description": "Returns a list of supported languages translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", + "security": [ + + ], + "parameters": [ + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + }, + "description": "Language locale code (e.g. \u0027en\u0027, \u0027hindi\u0027, \u0027sawahi\u0027, \u0027taglo\u0027, \u0027tamil\u0027)" + }, + { + "name": "Accept-Language", + "in": "header", + "schema": { + "type": "string" + }, + "description": "Fallback header locale (e.g. \u0027en\u0027, \u0027hi\u0027, \u0027sw\u0027, \u0027tl\u0027, \u0027ta\u0027)" + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search keyword to filter languages by name or code" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 500 + }, + "description": "Number of languages to return per page." + } + ], + "responses": { + "200": { + "description": "Languages list retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "languages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "en" + }, + "name": { + "type": "string", + "example": "English" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 22 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 2 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/preferred-locations": { + "get": { + "tags": [ + "General" + ], + "summary": "Get Localized Preferred Locations List", + "description": "Returns a list of preferred locations (Dubai, Abu Dhabi, Oman) translated to the requested language. Supports en, hindi (hi), sawahi (sw), taglo (tl), tamil (ta).", + "security": [ + + ], + "parameters": [ + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + }, + "description": "Language locale code (e.g. \u0027en\u0027, \u0027hindi\u0027, \u0027sawahi\u0027, \u0027taglo\u0027, \u0027tamil\u0027)" + }, + { + "name": "Accept-Language", + "in": "header", + "schema": { + "type": "string" + }, + "description": "Fallback header locale (e.g. \u0027en\u0027, \u0027hi\u0027, \u0027sw\u0027, \u0027tl\u0027, \u0027ta\u0027)" + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search keyword to filter locations by name or code" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 15 + }, + "description": "Number of locations to return per page" + } + ], + "responses": { + "200": { + "description": "Preferred locations list retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "preferred_locations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "dubai" + }, + "name": { + "type": "string", + "example": "Dubai" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 3 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 1 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/preferred-locations/{location}/areas": { + "get": { + "tags": [ + "General" + ], + "summary": "Get Localized Areas for a Preferred Location (Path Parameter)", + "description": "Returns a list of localized neighborhoods or sub-locations mapped under a specific preferred location (Dubai, Abu Dhabi, Oman) using path routing.", + "security": [ + + ], + "parameters": [ + { + "name": "location", + "in": "path", + "required": true, + "description": "The preferred location name (e.g. dubai, abu dhabi, oman).", + "schema": { + "type": "string" + } + }, + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + }, + "description": "Language locale code (e.g. \u0027en\u0027, \u0027hindi\u0027, \u0027sawahi\u0027, \u0027taglo\u0027, \u0027tamil\u0027)" + }, + { + "name": "Accept-Language", + "in": "header", + "schema": { + "type": "string" + }, + "description": "Fallback header locale (e.g. \u0027en\u0027, \u0027hi\u0027, \u0027sw\u0027, \u0027tl\u0027, \u0027ta\u0027)" + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search keyword to filter areas by name or code" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 15 + }, + "description": "Number of areas to return per page" + } + ], + "responses": { + "200": { + "description": "Areas retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "location": { + "type": "string", + "example": "dubai" + }, + "areas": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "marina" + }, + "name": { + "type": "string", + "example": "Dubai Marina" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 17 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 2 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/preferred-locations/areas": { + "get": { + "tags": [ + "General" + ], + "summary": "Get Localized Areas for a Preferred Location (Query Parameter)", + "description": "Returns a list of localized neighborhoods or sub-locations mapped under a specific preferred location (Dubai, Abu Dhabi, Oman) using query parameter routing.", + "security": [ + + ], + "parameters": [ + { + "name": "city", + "in": "query", + "required": false, + "description": "The preferred location name (e.g. dubai, abu dhabi, oman).", + "schema": { + "type": "string" + } + }, + { + "name": "location", + "in": "query", + "required": false, + "description": "Alternative key for location name (e.g. dubai, abu dhabi, oman).", + "schema": { + "type": "string" + } + }, + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + }, + "description": "Language locale code (e.g. \u0027en\u0027, \u0027hindi\u0027, \u0027sawahi\u0027, \u0027taglo\u0027, \u0027tamil\u0027)" + }, + { + "name": "Accept-Language", + "in": "header", + "schema": { + "type": "string" + }, + "description": "Fallback header locale (e.g. \u0027en\u0027, \u0027hi\u0027, \u0027sw\u0027, \u0027tl\u0027, \u0027ta\u0027)" + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search keyword to filter areas by name or code" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "per_page", + "in": "query", + "schema": { + "type": "integer", + "default": 15 + }, + "description": "Number of areas to return per page" + } + ], + "responses": { + "200": { + "description": "Areas retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "location": { + "type": "string", + "example": "dubai" + }, + "areas": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "example": "marina" + }, + "name": { + "type": "string", + "example": "Dubai Marina" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "example": 17 + }, + "per_page": { + "type": "integer", + "example": 15 + }, + "current_page": { + "type": "integer", + "example": 1 + }, + "last_page": { + "type": "integer", + "example": 2 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/workers/send-otp": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Send Mobile OTP Verification Code", + "description": "Dispatches a mock 6-digit OTP verification code (\u0027111111\u0027) to the worker\u0027s mobile phone number or email address.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971501234567", + "description": "Mobile contact phone number or identifier." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OTP dispatched successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Verification code sent. (Dev mock: 111111)" + }, + "identifier": { + "type": "string", + "example": "+971501234567" + } + } + } + } + } + } + } + } + }, + "/workers/verify-otp": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Verify Mobile OTP Code", + "description": "Validates the received 6-digit OTP code against the cached record. Sets a 1-hour secure gate allowing profile setup.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone", + "otp" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971501234567" + }, + "otp": { + "type": "string", + "example": "111111", + "description": "6-digit OTP received by the worker." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OTP verified and registration gate unlocked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "OTP verified successfully. You may proceed with profile setup." + }, + "identifier": { + "type": "string", + "example": "+971501234567" + } + } + } + } + } + }, + "422": { + "description": "Invalid OTP code or expired session." + } + } + } + }, + "/workers/setup-profile": { + "post": { + "tags": [ + "Worker/Auth" + ], + "summary": "Complete Basic Profile Setup", + "description": "Saves basic onboarding data (Name, Nationality, Language, Skills) and generates the permanent worker account along with a secure bearer access token.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone", + "name", + "nationality", + "language" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971501234567" + }, + "name": { + "type": "string", + "example": "Rahul Sharma" + }, + "nationality": { + "type": "string", + "example": "Indian" + }, + "language": { + "type": "string", + "enum": [ + "HI", + "SW", + "TL", + "TA" + ], + "example": "HI" + }, + "skills": { + "type": "array", + "items": { + "type": "integer" + }, + "example": [ + 6, + 8 + ], + "description": "Array of skill IDs" + }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male", + "description": "Gender of the worker (male, female, other)." + }, + "live_in_out": { + "type": "string", + "enum": [ + "live_in", + "live_out" + ], + "example": "live_out", + "description": "Accommodation preference (live_in, live_out)." + }, + "preferred_location": { + "type": "string", + "example": "Dubai Marina", + "description": "Preferred work location/area." + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Account created and profile configured successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Registration complete! Welcome to Migrant." + }, + "data": { + "type": "object", + "properties": { + "worker": { + "$ref": "#/components/schemas/Worker" + }, + "token": { + "type": "string", + "example": "abc123xyz456...secureToken..." + } + } + } + } + } + } + } + } + } + } + }, + "/workers/profile": { + "get": { + "tags": [ + "Worker/Profile" + ], + "summary": "Get Profile details", + "description": "Retrieves the authenticated worker\u0027s profile details including their selected job category, connected skills, and uploaded documents.", + "responses": { + "200": { + "description": "Worker profile details retrieved.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "worker": { + "$ref": "#/components/schemas/Worker" + } + } + } + } + } + } + } + } + } + } + }, + "/workers/profile/update": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Update Profile details", + "description": "Allows workers to customize and complete their profiles by updating age, nationality, desired salary, bio, availability, experience, religion, categories, and master skills list.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Amina Al-Masry" + }, + "age": { + "type": "integer", + "example": 28 + }, + "nationality": { + "type": "string", + "example": "Egypt" + }, + "salary": { + "type": "number", + "example": 1800 + }, + "availability": { + "type": "string", + "example": "Immediate" + }, + "experience": { + "type": "string", + "example": "4 Years" + }, + "religion": { + "type": "string", + "example": "Muslim" + }, + "bio": { + "type": "string", + "example": "Highly certified housekeeper and babysitter with cooking experience." + }, + "skills": { + "type": "array", + "items": { + "type": "integer" + }, + "example": [ + 6, + 8 + ] + }, + "fcm_token": { + "type": "string", + "example": "fcm-device-token-xyz-123", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Profile updated successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Profile updated successfully." + }, + "data": { + "type": "object", + "properties": { + "worker": { + "$ref": "#/components/schemas/Worker" + } + } + } + } + } + } + } + } + } + } + }, + "/workers/go-live": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Profile Go Live", + "description": "Sets the worker status to \u0027active\u0027 and availability to \u0027Immediate\u0027, making the profile searchable on the sponsor/employer marketplace.", + "responses": { + "200": { + "description": "Worker profile is now live and searchable.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Your profile is now live! Status set to Active." + } + } + } + } + } + } + } + } + }, + "/workers/profile/toggle-availability": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Toggle Profile Search Visibility", + "description": "Updates worker search visibility based on whether they are still actively seeking a job. \u0027still_looking\u0027 as true makes profile active, false hides it.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "still_looking" + ], + "properties": { + "still_looking": { + "type": "boolean", + "example": true, + "description": "Whether the worker is still actively looking for job offers." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Availability toggled successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "still_looking": { + "type": "boolean", + "example": true + }, + "status": { + "type": "string", + "example": "active" + } + } + } + } + } + } + } + } + }, + "/workers/profile/mark-hired": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Mark Worker as Hired", + "description": "Changes the worker\u0027s status to \u0027Hired\u0027, which automatically removes them from active marketplace searches.", + "responses": { + "200": { + "description": "Worker marked as Hired.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Congratulations! You have been successfully marked as Hired." + } + } + } + } + } + } + } + } + }, + "/workers/offers": { + "get": { + "tags": [ + "Worker/Offers" + ], + "summary": "View Received Job Offers", + "description": "Returns a list of all custom job/hiring offers received by the worker from different employers (e.g. Work Date, location, salaries, and notes).", + "responses": { + "200": { + "description": "List of job offers retrieved.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "offers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JobOffer" + } + } + } + } + } + } + } + } + } + } + } + }, + "/workers/offers/{id}/respond": { + "post": { + "tags": [ + "Worker/Offers" + ], + "summary": "Accept or Reject Job Offer", + "description": "Responds to a pending job offer. If accepted, the job offer status becomes \u0027accepted\u0027 and the worker\u0027s status is automatically changed to \u0027Hired\u0027, which reflects immediately in the Employer panel.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The Job Offer ID reference.", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "accepted", + "rejected" + ], + "example": "accepted", + "description": "Response action (accepted or rejected)." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Job offer response saved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Job offer successfully accepted." + }, + "data": { + "type": "object", + "properties": { + "offer": { + "$ref": "#/components/schemas/JobOffer" + }, + "worker_status": { + "type": "string", + "example": "Hired" + } + } + } + } + } + } + } + } + } + } + }, + "/workers/conversations": { + "get": { + "tags": [ + "Worker/Conversations" + ], + "summary": "Get Conversations List (Worker)", + "description": "Retrieves all active conversations for the authenticated worker.", + "responses": { + "200": { + "description": "Conversations list retrieved successfully." + } + } + } + }, + "/workers/conversations/{id}/messages": { + "get": { + "tags": [ + "Worker/Conversations" + ], + "summary": "Get Conversation Messages (Worker)", + "description": "Retrieves message history for a conversation and marks incoming employer messages as read.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Messages retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Worker/Conversations" + ], + "summary": "Send Reply Message (Worker)", + "description": "Sends a reply message from the worker to the employer.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string", + "example": "Yes, I am available.", + "description": "Optional text message content (required if no file is uploaded)." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Optional file or voice note attachment (Max 10MB). Supports images, documents, and common audio formats." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Message sent successfully." + } + } + } + }, + "/workers/report": { + "post": { + "tags": [ + "Worker/Profile" + ], + "summary": "Submit a Safety/Moderation Report (Worker)", + "description": "Submits a report on a chat conversation or a review. The report is logged in the safety queue and reviewed by administrators.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "type", + "item_id", + "reason" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Chat", + "Review" + ], + "example": "Chat", + "description": "The type of entity being reported (Chat or Review)." + }, + "item_id": { + "type": "string", + "example": "1", + "description": "The ID of the conversation or review being reported." + }, + "user_id": { + "type": "string", + "example": "5", + "description": "Optional ID of the user being reported." + }, + "reason": { + "type": "string", + "example": "Others", + "description": "The reason for submitting this report." + }, + "others": { + "type": "string", + "example": "Spam and malicious links", + "description": "Custom reason string. Used only if reason is \u0027Others\u0027." + }, + "description": { + "type": "string", + "example": "The other party sent highly inappropriate and insulting messages.", + "description": "Optional detailed explanation/comments." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Report submitted successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Report submitted successfully. Our safety team will review it shortly." + }, + "report_id": { + "type": "string", + "example": "REP-ABCD1234" + } + } + } + } + } + }, + "404": { + "description": "Conversation or Review not found, or user is not authorized to report it." + }, + "422": { + "description": "Validation error." + } + } + } + }, + "/workers/report-reasons": { + "get": { + "tags": [ + "Worker/Profile" + ], + "summary": "Get Safety Report Reasons (Worker)", + "description": "Returns a list of active safety/moderation report reasons. Can filter reasons by target type (Chat or Review).", + "parameters": [ + { + "name": "type", + "in": "query", + "required": false, + "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", + "schema": { + "type": "string", + "enum": [ + "Chat", + "Review" + ] + } + } + ], + "responses": { + "200": { + "description": "List of report reasons retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "reasons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "reason": { + "type": "string", + "example": "Abusive language / Profanity" + }, + "type": { + "type": "string", + "example": "Both" + } + } + } + } + } + } + } + } + } + } + } + }, + "/workers/tickets": { + "get": { + "tags": [ + "Worker/Support" + ], + "summary": "List Support Tickets (Worker)", + "description": "Retrieves a paginated list of all support tickets created by the authenticated worker.", + "responses": { + "200": { + "description": "Tickets retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Worker/Support" + ], + "summary": "Create Support Ticket (Worker)", + "description": "Creates a support ticket for a worker with a subject, description, and optional priority level.", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "subject", + "description" + ], + "properties": { + "subject": { + "type": "string", + "example": "App login crashes on launch" + }, + "description": { + "type": "string", + "example": "When opening the app on my phone, it crashes immediately on the splash screen." + }, + "priority": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "example": "medium" + }, + "reason_id": { + "type": "integer", + "example": 1, + "description": "Optional report/support reason ID." + }, + "voice_note": { + "type": "string", + "format": "binary", + "description": "Optional audio/voice note file attachment." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Ticket created successfully." + } + } + } + }, + "/workers/tickets/{id}": { + "get": { + "tags": [ + "Worker/Support" + ], + "summary": "Get Support Ticket Details \u0026 Replies (Worker)", + "description": "Retrieves the details of a support ticket and its chronological reply history.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Ticket details and replies retrieved successfully." + } + } + } + }, + "/workers/tickets/{id}/reply": { + "post": { + "tags": [ + "Worker/Support" + ], + "summary": "Reply to Support Ticket (Worker)", + "description": "Submits a text reply or message to an active, open, or resolved support ticket.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "I tried clearing cache and it still crashes." + }, + "voice_note": { + "type": "string", + "format": "binary", + "description": "Optional audio/voice note file attachment." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Reply posted successfully." + } + } + } + }, + "/employers/register": { + "post": { + "tags": [ + "Employer/Auth" + ], + "summary": "Register Sponsor (Alias)", + "description": "Legacy route matching the simplified sponsor registration (Name, Email, Phone) (Step 1).", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "email", + "phone" + ], + "properties": { + "name": { + "type": "string", + "example": "Ahmad Bin Ahmed", + "description": "Sponsor\u0027s full name" + }, + "email": { + "type": "string", + "example": "ahmad@example.com", + "description": "Sponsor\u0027s contact email address" + }, + "phone": { + "type": "string", + "example": "+971509990001", + "description": "Sponsor\u0027s mobile phone number" + }, + "emirates_id": { + "type": "object", + "description": "Optional JSON object containing extracted Emirates ID details.", + "properties": { + "emirates_id_number": { + "type": "string", + "example": "784-1988-5310327-2" + }, + "name": { + "type": "string", + "example": "Ahmad Bin Ahmed" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "nationality": { + "type": "string", + "example": "Emirati" + }, + "issue_date": { + "type": "string", + "example": "2023-04-11" + }, + "expiry_date": { + "type": "string", + "example": "2028-04-11" + }, + "employer": { + "type": "string", + "example": "Federal Authority" + }, + "issue_place": { + "type": "string", + "example": "Abu Dhabi" + }, + "occupation": { + "type": "string", + "example": "Manager" + } + } + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + }, + "address": { + "type": "string", + "example": "Villa 45, Street 12", + "description": "Physical address/location details." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Sponsor registered successfully." + } + } + } + }, + "/employers/verify": { + "post": { + "tags": [ + "Employer/Auth" + ], + "summary": "Verify Sponsor Email OTP", + "description": "Verifies the email address with the OTP verification code (Step 2).", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "otp" + ], + "properties": { + "email": { + "type": "string", + "example": "ahmad@example.com" + }, + "otp": { + "type": "string", + "example": "111111", + "description": "6-digit OTP code" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Email verified successfully." + } + } + } + }, + "/employers/payment": { + "post": { + "tags": [ + "Employer/Auth" + ], + "summary": "Sponsor Subscription Payment", + "description": "Submits a successful plan selection and PayTabs payment confirmation (Step 4).", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "plan_id", + "amount_aed", + "paytabs_transaction_id" + ], + "properties": { + "email": { + "type": "string", + "example": "ahmad@example.com" + }, + "plan_id": { + "type": "string", + "example": "premium" + }, + "amount_aed": { + "type": "number", + "example": 199 + }, + "paytabs_transaction_id": { + "type": "string", + "example": "TXN998877" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Subscription payment confirmed." + } + } + } + }, + "/employers/password": { + "post": { + "tags": [ + "Employer/Auth" + ], + "summary": "Create Sponsor Account Password", + "description": "Configures and finalizes the portal login password to complete registration (Step 5). Returns bearer token.", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password", + "password_confirmation" + ], + "properties": { + "email": { + "type": "string", + "example": "ahmad@example.com" + }, + "password": { + "type": "string", + "format": "password", + "example": "Password@123" + }, + "password_confirmation": { + "type": "string", + "format": "password", + "example": "Password@123" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password set and registration finalized successfully." + } + } + } + }, + "/employers/plans": { + "get": { + "tags": [ + "Employer/Auth" + ], + "summary": "Get Available Subscription Plans", + "description": "Returns details (price, features) of subscription packages available to sponsors.", + "security": [ + + ], + "responses": { + "200": { + "description": "Subscription plans list retrieved successfully." + } + } + } + }, + "/employers/login": { + "post": { + "tags": [ + "Employer/Auth" + ], + "summary": "Authenticate Employer or Sponsor", + "description": "Unified authentication endpoint. Validates email/mobile and password, then returns a stateless Bearer token and the user\u0027s role (employer or sponsor).", + "security": [ + + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "password" + ], + "properties": { + "email": { + "type": "string", + "example": "ahmad@example.com", + "description": "Email address (required if mobile is not provided)" + }, + "mobile": { + "type": "string", + "example": "+971509990001", + "description": "Mobile number (required if email is not provided)" + }, + "password": { + "type": "string", + "example": "Password@123" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Login successful. Returns Bearer token and role.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Login successful." + }, + "role": { + "type": "string", + "example": "employer", + "enum": [ + "employer", + "sponsor" + ] + }, + "data": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "api_token_here" + } + } + } + } + } + } + } + } + } + } + }, + "/employers/conversations": { + "get": { + "tags": [ + "Employer/Conversations" + ], + "summary": "Get Conversations List (Employer)", + "description": "Retrieves all active candidate conversations for the authenticated employer.", + "responses": { + "200": { + "description": "Conversations list retrieved successfully." + } + } + } + }, + "/employers/conversations/{id}/messages": { + "get": { + "tags": [ + "Employer/Conversations" + ], + "summary": "Get Conversation Messages (Employer)", + "description": "Retrieves message history for a conversation and marks incoming worker messages as read.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Messages retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Employer/Conversations" + ], + "summary": "Send Reply Message (Employer)", + "description": "Sends a reply message from the employer to the worker.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string", + "example": "When can you start?", + "description": "Optional text message content (required if no file is uploaded)." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Optional file or voice note attachment (Max 10MB). Supports images, documents, and common audio formats." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Message sent successfully." + } + } + } + }, + "/employers/conversations/start": { + "post": { + "tags": [ + "Employer/Conversations" + ], + "summary": "Start Conversation with Worker", + "description": "Starts or retrieves a conversation with the specified worker ID.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "worker_id" + ], + "properties": { + "worker_id": { + "type": "integer", + "example": 1 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Conversation started successfully." + } + } + } + }, + "/workers/announcements": { + "get": { + "tags": [ + "Worker/CharityEvents" + ], + "summary": "Get Charity Events (Worker)", + "description": "Allows workers to retrieve the list of active charity events, food drives, and medical support programs scheduled in Dubai.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "List of charity events retrieved successfully." + } + } + } + }, + "/workers/announcements/new": { + "get": { + "tags": [ + "Worker/CharityEvents" + ], + "summary": "Get New/Unviewed Announcements/Charity Events", + "description": "Retrieves up to 3 active approved charity events/announcements that the worker has not yet viewed, and automatically marks them as viewed.", + "responses": { + "200": { + "description": "Successfully retrieved new announcements.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 4 + }, + "title": { + "type": "string", + "example": "Free Dental Checkup Camp" + }, + "body": { + "type": "string", + "example": "Emirates Charity is providing free screening." + }, + "type": { + "type": "string", + "example": "charity" + }, + "employer_name": { + "type": "string", + "example": "Emirates Charity" + }, + "company_name": { + "type": "string", + "example": "Emirates Charity Foundation" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-01T10:00:00.000000Z" + }, + "time_ago": { + "type": "string", + "example": "5 hours ago" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthenticated." + } + } + } + }, + "/workers/announcements/{id}/view": { + "post": { + "tags": [ + "Worker/CharityEvents" + ], + "summary": "Mark Specific Announcement as Viewed", + "description": "Explicitly marks a specific announcement/charity event as viewed by the authenticated worker.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Announcement ID", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Announcement marked as viewed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Announcement marked as viewed." + } + } + } + } + } + }, + "401": { + "description": "Unauthenticated." + }, + "404": { + "description": "Announcement not found." + } + } + } + }, + "/employers/announcements": { + "get": { + "tags": [ + "Employer/CharityEvents" + ], + "summary": "Get Posted Charity Events (Employer)", + "description": "Allows employers/sponsors to retrieve the list of charity events they have published.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "List of posted charity events retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Employer/CharityEvents" + ], + "summary": "Publish Charity Event (Employer)", + "description": "Allows employers/sponsors to publish a new community charity event or drive to all workers in Dubai, triggering instant push notifications and morning-of reminders.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "content", + "provided_items", + "event_date", + "event_time", + "location_details", + "location_pin" + ], + "properties": { + "title": { + "type": "string", + "example": "Free Dental Checkup Camp" + }, + "content": { + "type": "string", + "example": "Emirates Charity is providing free professional dental screening, cleanings, and educational packages for domestic helpers." + }, + "provided_items": { + "type": "string", + "example": "Free Dental screening, cleanings, and wellness kits" + }, + "event_date": { + "type": "string", + "format": "date", + "example": "2026-06-15" + }, + "event_time": { + "type": "string", + "example": "9:00 AM - 4:00 PM" + }, + "location_details": { + "type": "string", + "example": "Al Quoz Community Hall, Dubai" + }, + "location_pin": { + "type": "string", + "format": "uri", + "example": "https://maps.app.goo.gl/xyz" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Charity event created and notifications scheduled successfully." + } + } } - }, - "conversation_id": { - "type": "integer", - "nullable": true, - "example": 4 - } - } - } - } - } - } - } - } - } - }, - "404": { - "description": "Worker profile not found." - } - } - } - }, - "/employers/candidates": { - "get": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Get Candidate Applications and Offers", - "description": "Returns all job applications and direct hiring offers connected to the authenticated sponsor account that have achieved 'Hired' status.", - "parameters": [ - { - "name": "search", - "in": "query", - "required": false, - "description": "Search term matching helper name, nationality, or religion.", - "schema": { - "type": "string" - } - }, - { - "name": "skills", - "in": "query", - "required": false, - "description": "Filter candidates by skills (comma-separated list of skill names, e.g. cooking,driving).", - "schema": { - "type": "string" - } - }, - { - "name": "languages", - "in": "query", - "required": false, - "description": "Filter candidates by languages spoken (comma-separated list of language names, e.g. English,Arabic).", - "schema": { - "type": "string" - } - }, - { - "name": "nationality", - "in": "query", - "required": false, - "description": "Filter candidates by nationality name (comma-separated list for multiple choice, e.g. Indian,Kenyan,Ethiopian).", - "schema": { - "type": "string" - } - }, - { - "name": "gender", - "in": "query", - "required": false, - "description": "Filter candidates by gender (e.g. male, female).", - "schema": { - "type": "string" - } - }, - { - "name": "preferred_location", - "in": "query", - "required": false, - "description": "Filter candidates by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).", - "schema": { - "type": "string" - } - }, - { - "name": "job_type", - "in": "query", - "required": false, - "description": "Filter candidates by job type (comma-separated list, e.g. full-time,part-time).", - "schema": { - "type": "string" - } - }, - { - "name": "accommodation_type", - "in": "query", - "required": false, - "description": "Filter candidates by accommodation type (comma-separated list, e.g. live_in,live_out).", - "schema": { - "type": "string" - } - }, - { - "name": "in_country", - "in": "query", - "required": false, - "description": "Filter candidates by in country status (true/false or yes/no or in/out).", - "schema": { - "type": "string" - } - }, - { - "name": "out_country", - "in": "query", - "required": false, - "description": "Filter candidates by out country status (true/false or yes/no or out/in).", - "schema": { - "type": "string" - } - }, - { - "name": "visa_status", - "in": "query", - "required": false, - "description": "Filter candidates by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.", - "schema": { - "type": "string" - } - }, - { - "name": "availability", - "in": "query", - "required": false, - "description": "Filter candidates by availability status.", - "schema": { - "type": "string" - } - }, - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "Employer candidate pipeline retrieved successfully." - } - } - } - }, - "/employers/candidates/{id}/hire": { - "post": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Mark Candidate as Hired", - "description": "Transitions the target candidate application, job offer, or helper status to 'Hired'.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "The target identifier (Application ID, Offer ID, or Worker ID).", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Candidate successfully status updated to Hired." - } - } - } - }, - "/employers/shortlist": { - "get": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Get Shortlisted Workers", - "description": "Returns a list of all workers shortlisted by the authenticated sponsor.", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "Shortlisted workers retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Employer/Pipeline" - ], - "summary": "Toggle Worker Shortlist Status", - "description": "Toggles (adds or removes) a worker to/from the authenticated sponsor's shortlist.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "worker_id" - ], - "properties": { - "worker_id": { - "type": "integer", - "example": 1, - "description": "Worker ID to shortlist/unshortlist." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Shortlist status toggled successfully." - } - } - } - }, - "/employers/payments": { - "get": { - "tags": [ - "Employer/Billing" - ], - "summary": "Get Payment History", - "description": "Retrieves the complete transaction history for the authenticated sponsor/employer, including subscription passes and document verification receipts.", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "Employer billing history retrieved successfully." - } - } - } - }, - "/workers/dashboard/views": { - "get": { - "tags": [ - "Worker/Dashboard" - ], - "summary": "Get Worker Profile Views Statistics", - "description": "Allows authenticated workers to see the total count and detailed list of employers/sponsors who viewed their full profile.", - "responses": { - "200": { - "description": "Profile views statistics retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "views_count": { - "type": "integer", - "example": 5 - }, - "views_list": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "employer_id": { - "type": "integer", - "example": 2 - }, - "employer_name": { - "type": "string", - "example": "Ahmad" - }, - "company_name": { - "type": "string", - "example": "Ahmad Tech Ltd" - }, - "nationality": { - "type": "string", - "example": "UAE" - }, - "city": { - "type": "string", - "example": "Dubai" - }, - "viewed_at": { - "type": "string", - "format": "date-time", - "example": "2026-06-01T15:10:24.000000Z" - }, - "viewed_at_formatted": { - "type": "string", - "example": "2 minutes ago" - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/workers/dashboard": { - "get": { - "tags": [ - "Worker/Dashboard" - ], - "summary": "Get Worker Consolidated Dashboard Data", - "description": "Allows authenticated workers to retrieve their dashboard metrics: active status, profile views count, currently working sponsor/employer details, and latest charity events/announcements.", - "responses": { - "200": { - "description": "Worker dashboard details retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "data": { - "type": "object", - "properties": { - "active_status": { - "type": "string", - "example": "active" - }, - "count_sponsors_viewed": { - "type": "integer", - "example": 5 - }, - "employer_contacted": { - "type": "integer", - "example": 2, - "description": "Unique count of employers who contacted this worker." - }, - "profile_viewed": { - "type": "integer", - "example": 3, - "description": "Unique count of employers who viewed this worker's profile." - }, - "currently_working_sponsor": { - "type": "object", - "nullable": true, - "properties": { - "id": { - "type": "integer", - "example": 2 - }, - "name": { - "type": "string", - "example": "Ahmad" - }, - "company_name": { - "type": "string", - "example": "Ahmad Tech Ltd" - }, - "nationality": { - "type": "string", - "example": "UAE" - }, - "city": { - "type": "string", - "example": "Dubai" - }, - "email": { - "type": "string", - "example": "ahmad@example.com" - }, - "phone": { - "type": "string", - "example": "+971509990001" - }, - "hired_at": { - "type": "string", - "format": "date-time", - "example": "2026-06-01T15:10:24.000000Z" - }, - "hired_at_formatted": { - "type": "string", - "example": "Jun 01, 2026" - } - } - }, - "latest_charity_events_list": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "title": { - "type": "string", - "example": "Free Dental Checkup Camp" - }, - "body": { - "type": "string", - "example": "Emirates Charity is providing free screening." - }, - "type": { - "type": "string", - "example": "charity" - }, - "employer_name": { - "type": "string", - "example": "Emirates Charity" - }, - "company_name": { - "type": "string", - "example": "Emirates Charity Foundation" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2026-06-01T10:00:00.000000Z" - }, - "time_ago": { - "type": "string", - "example": "5 hours ago" - } - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/employers/reviews": { - "get": { - "tags": [ - "Employer/Reviews" - ], - "summary": "Get Reviews Written by Employer", - "description": "Retrieves a paginated list of all reviews written by the authenticated employer.", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "description": "Page number.", - "schema": { - "type": "integer", - "default": 1 - } - }, - { - "name": "per_page", - "in": "query", - "required": false, - "description": "Number of items per page.", - "schema": { - "type": "integer", - "default": 15 - } - } - ], - "responses": { - "200": { - "description": "Reviews retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Employer/Reviews" - ], - "summary": "Add or Update Worker Review", - "description": "Allows employers to submit a review score and description comment for a worker. If the employer has already left a review, this will update it.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "worker_id", - "rating" - ], - "properties": { - "worker_id": { - "type": "integer", - "example": 136, - "description": "Worker ID being reviewed." - }, - "rating": { - "type": "integer", - "minimum": 1, - "maximum": 5, - "example": 5, - "description": "Review score from 1 to 5." - }, - "comment": { - "type": "string", - "example": "Highly professional, punctual and did a great job!", - "description": "Optional detailed comment." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Review updated successfully." - }, - "201": { - "description": "Review added successfully." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/employers/reviews/{id}": { - "put": { - "tags": [ - "Employer/Reviews" - ], - "summary": "Edit Worker Review", - "description": "Allows employers to edit their previously created review score and comment by review ID.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Review ID reference.", - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "rating" - ], - "properties": { - "rating": { - "type": "integer", - "minimum": 1, - "maximum": 5, - "example": 4, - "description": "Updated rating score from 1 to 5." - }, - "comment": { - "type": "string", - "example": "Updated feedback comment.", - "description": "Updated review comment description." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Review edited successfully." - }, - "404": { - "description": "Review not found or unauthorized." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/employers/report": { - "post": { - "tags": [ - "Employer/Reviews" - ], - "summary": "Submit a Safety/Moderation Report (Employer)", - "description": "Submits a report on a chat conversation or a review. The report is logged in the safety queue and reviewed by administrators.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "type", - "item_id", - "reason" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "Chat", - "Review" - ], - "example": "Chat", - "description": "The type of entity being reported (Chat or Review)." - }, - "item_id": { - "type": "string", - "example": "1", - "description": "The ID of the conversation or review being reported." - }, - "user_id": { - "type": "string", - "example": "5", - "description": "Optional ID of the user being reported." - }, - "reason": { - "type": "string", - "example": "Others", - "description": "The reason for submitting this report." - }, - "others": { - "type": "string", - "example": "Spam and malicious links", - "description": "Custom reason string. Used only if reason is 'Others'." - }, - "description": { - "type": "string", - "example": "The worker requested payments off-platform and sent abusive messages.", - "description": "Optional detailed explanation/comments." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Report submitted successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Report submitted successfully. Our safety team will review it shortly." - }, - "report_id": { - "type": "string", - "example": "REP-ABCD1234" - } - } - } - } - } - }, - "404": { - "description": "Conversation or Review not found, or user is not authorized to report it." - }, - "422": { - "description": "Validation error." - } - } - } - }, - "/employers/report-reasons": { - "get": { - "tags": [ - "Employer/Reviews" - ], - "summary": "Get Safety Report Reasons (Employer)", - "description": "Returns a list of active safety/moderation report reasons. Can filter reasons by target type (Chat or Review).", - "parameters": [ - { - "name": "type", - "in": "query", - "required": false, - "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", - "schema": { - "type": "string", - "enum": [ - "Chat", - "Review" - ] - } - } - ], - "responses": { - "200": { - "description": "List of report reasons retrieved successfully.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "reasons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 + }, + "/employers/announcements/{id}": { + "delete": { + "tags": [ + "Employer/CharityEvents" + ], + "summary": "Delete Charity Event (Employer)", + "description": "Allows employers/sponsors to delete a previously published charity event.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Charity event deleted successfully." + } + } + } }, - "reason": { - "type": "string", - "example": "Abusive language / Profanity" + "/employers/dashboard": { + "get": { + "tags": [ + "Employer/Profile" + ], + "summary": "Get Employer Dashboard stats \u0026 recent events", + "description": "Retrieves stats including contacted workers count, total hired workers, saved candidates, current plan details, and recent announcements or events.", + "responses": { + "200": { + "description": "Employer dashboard stats retrieved successfully." + } + } + } + }, + "/employers/profile": { + "get": { + "tags": [ + "Employer/Profile" + ], + "summary": "Get Profile details (Employer)", + "description": "Retrieves the authenticated employer\u0027s profile details including their selected company name, phone, language preference, and notification settings.", + "responses": { + "200": { + "description": "Employer profile details retrieved successfully." + } + } + } + }, + "/employers/profile/update": { + "post": { + "tags": [ + "Employer/Profile" + ], + "summary": "Update Profile details (Employer)", + "description": "Allows employers to update their profile details (company name, phone number, name, email, language preference, and notification settings) and change their account password.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "email", + "phone", + "company_name", + "language", + "notifications" + ], + "properties": { + "name": { + "type": "string", + "example": "Ahmad" + }, + "email": { + "type": "string", + "example": "ahmad@example.com" + }, + "phone": { + "type": "string", + "example": "+971509990001" + }, + "company_name": { + "type": "string", + "example": "Ahmad Tech Ltd" + }, + "language": { + "type": "string", + "enum": [ + "English", + "Arabic", + "english", + "arabic" + ], + "example": "English" + }, + "notifications": { + "type": "boolean", + "example": true + }, + "current_password": { + "type": "string", + "example": "Password@123" + }, + "new_password": { + "type": "string", + "example": "NewSecurePassword@123" + }, + "new_password_confirmation": { + "type": "string", + "example": "NewSecurePassword@123" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example", + "description": "Firebase Cloud Messaging token for push notifications." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Profile updated successfully." + } + } + } + }, + "/employers/workers": { + "get": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Get Available Workers", + "description": "Returns a list of verified workers that are currently available to be hired.", + "parameters": [ + { + "name": "search", + "in": "query", + "required": false, + "description": "Search term matching helper name, nationality, or religion.", + "schema": { + "type": "string" + } + }, + { + "name": "skills", + "in": "query", + "required": false, + "description": "Filter by skills (comma-separated list).", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "required": false, + "description": "Filter by language.", + "schema": { + "type": "string" + } + }, + { + "name": "nationality", + "in": "query", + "required": false, + "description": "Filter by nationality name (comma-separated list for multiple choice, e.g. Indian,Kenyan,Ethiopian).", + "schema": { + "type": "string" + } + }, + { + "name": "gender", + "in": "query", + "required": false, + "description": "Filter by gender (e.g. male, female).", + "schema": { + "type": "string" + } + }, + { + "name": "preferred_location", + "in": "query", + "required": false, + "description": "Filter by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).", + "schema": { + "type": "string" + } + }, + { + "name": "area", + "in": "query", + "required": false, + "description": "Filter by preferred area (comma-separated list, e.g. Marina,Al Barsha).", + "schema": { + "type": "string" + } + }, + { + "name": "job_type", + "in": "query", + "required": false, + "description": "Filter by job type (comma-separated list, e.g. full-time,part-time).", + "schema": { + "type": "string" + } + }, + { + "name": "accommodation_type", + "in": "query", + "required": false, + "description": "Filter by accommodation type (comma-separated list, e.g. live_in,live_out).", + "schema": { + "type": "string" + } + }, + { + "name": "in_country", + "in": "query", + "required": false, + "description": "Filter by in country status (true/false or yes/no or in/out).", + "schema": { + "type": "string" + } + }, + { + "name": "out_country", + "in": "query", + "required": false, + "description": "Filter by out country status (true/false or yes/no or out/in).", + "schema": { + "type": "string" + } + }, + { + "name": "visa_status", + "in": "query", + "required": false, + "description": "Filter by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.", + "schema": { + "type": "string" + } + }, + { + "name": "availability", + "in": "query", + "required": false, + "description": "Filter by availability status.", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "Available workers list retrieved successfully." + } + } + } + }, + "/employers/workers/{id}": { + "get": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Get Worker Profile Detail", + "description": "Retrieves the complete profile details for a worker, mirroring the web detailed view, along with active conversation details if already started with this employer.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Worker ID reference.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Worker profile details retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "worker": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "Jane Doe" + }, + "nationality": { + "type": "string", + "example": "Filipino" + }, + "photo": { + "type": "string", + "example": "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format\u0026fit=crop\u0026q=80\u0026w=200" + }, + "emirates_id_status": { + "type": "string", + "example": "Passport Verified", + "description": "Deprecated - Use passport_status instead" + }, + "passport_status": { + "type": "string", + "example": "Passport Verified" + }, + "category": { + "type": "string", + "example": "Domestic Worker" + }, + "skills": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "childcare", + "cooking" + ] + }, + "availability_status": { + "type": "string", + "example": "Active" + }, + "visa_status": { + "type": "string", + "example": "Residence Visa" + }, + "experience": { + "type": "string", + "example": "5 Years" + }, + "experience_years": { + "type": "integer", + "example": 5 + }, + "salary": { + "type": "integer", + "example": 2500 + }, + "religion": { + "type": "string", + "example": "Christian" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "English", + "Tagalog" + ] + }, + "age": { + "type": "integer", + "example": 28 + }, + "verified": { + "type": "boolean", + "example": true + }, + "preferred_job_type": { + "type": "string", + "example": "live-in" + }, + "bio": { + "type": "string", + "example": "Experienced and caring domestic worker specialing in childcare and housekeeping..." + }, + "rating": { + "type": "number", + "example": 4.3 + }, + "reviews_count": { + "type": "integer", + "example": 6 + }, + "reviews": { + "type": "array", + "items": { + "type": "object" + } + }, + "similar_workers": { + "type": "array", + "items": { + "type": "object" + } + }, + "conversation_id": { + "type": "integer", + "nullable": true, + "example": 4 + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Worker profile not found." + } + } + } + }, + "/employers/candidates": { + "get": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Get Candidate Applications and Offers", + "description": "Returns all job applications and direct hiring offers connected to the authenticated sponsor account that have achieved \u0027Hired\u0027 status.", + "parameters": [ + { + "name": "search", + "in": "query", + "required": false, + "description": "Search term matching helper name, nationality, or religion.", + "schema": { + "type": "string" + } + }, + { + "name": "skills", + "in": "query", + "required": false, + "description": "Filter candidates by skills (comma-separated list of skill names, e.g. cooking,driving).", + "schema": { + "type": "string" + } + }, + { + "name": "languages", + "in": "query", + "required": false, + "description": "Filter candidates by languages spoken (comma-separated list of language names, e.g. English,Arabic).", + "schema": { + "type": "string" + } + }, + { + "name": "nationality", + "in": "query", + "required": false, + "description": "Filter candidates by nationality name (comma-separated list for multiple choice, e.g. Indian,Kenyan,Ethiopian).", + "schema": { + "type": "string" + } + }, + { + "name": "gender", + "in": "query", + "required": false, + "description": "Filter candidates by gender (e.g. male, female).", + "schema": { + "type": "string" + } + }, + { + "name": "preferred_location", + "in": "query", + "required": false, + "description": "Filter candidates by preferred location (comma-separated list, e.g. Dubai,Abu Dhabi).", + "schema": { + "type": "string" + } + }, + { + "name": "job_type", + "in": "query", + "required": false, + "description": "Filter candidates by job type (comma-separated list, e.g. full-time,part-time).", + "schema": { + "type": "string" + } + }, + { + "name": "accommodation_type", + "in": "query", + "required": false, + "description": "Filter candidates by accommodation type (comma-separated list, e.g. live_in,live_out).", + "schema": { + "type": "string" + } + }, + { + "name": "in_country", + "in": "query", + "required": false, + "description": "Filter candidates by in country status (true/false or yes/no or in/out).", + "schema": { + "type": "string" + } + }, + { + "name": "out_country", + "in": "query", + "required": false, + "description": "Filter candidates by out country status (true/false or yes/no or out/in).", + "schema": { + "type": "string" + } + }, + { + "name": "visa_status", + "in": "query", + "required": false, + "description": "Filter candidates by next visa status (comma-separated list, e.g. Residence Visa,Tourist Visa). Only applicable if in country.", + "schema": { + "type": "string" + } + }, + { + "name": "availability", + "in": "query", + "required": false, + "description": "Filter candidates by availability status.", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "Employer candidate pipeline retrieved successfully." + } + } + } + }, + "/employers/candidates/{id}/hire": { + "post": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Mark Candidate as Hired", + "description": "Transitions the target candidate application, job offer, or helper status to \u0027Hired\u0027.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The target identifier (Application ID, Offer ID, or Worker ID).", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Candidate successfully status updated to Hired." + } + } + } + }, + "/employers/shortlist": { + "get": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Get Shortlisted Workers", + "description": "Returns a list of all workers shortlisted by the authenticated sponsor.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "Shortlisted workers retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Employer/Pipeline" + ], + "summary": "Toggle Worker Shortlist Status", + "description": "Toggles (adds or removes) a worker to/from the authenticated sponsor\u0027s shortlist.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "worker_id" + ], + "properties": { + "worker_id": { + "type": "integer", + "example": 1, + "description": "Worker ID to shortlist/unshortlist." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Shortlist status toggled successfully." + } + } + } + }, + "/employers/payments": { + "get": { + "tags": [ + "Employer/Billing" + ], + "summary": "Get Payment History", + "description": "Retrieves the complete transaction history for the authenticated sponsor/employer, including subscription passes and document verification receipts.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "Employer billing history retrieved successfully." + } + } + } + }, + "/workers/dashboard/views": { + "get": { + "tags": [ + "Worker/Dashboard" + ], + "summary": "Get Worker Profile Views Statistics", + "description": "Allows authenticated workers to see the total count and detailed list of employers/sponsors who viewed their full profile.", + "responses": { + "200": { + "description": "Profile views statistics retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "views_count": { + "type": "integer", + "example": 5 + }, + "views_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "employer_id": { + "type": "integer", + "example": 2 + }, + "employer_name": { + "type": "string", + "example": "Ahmad" + }, + "company_name": { + "type": "string", + "example": "Ahmad Tech Ltd" + }, + "nationality": { + "type": "string", + "example": "UAE" + }, + "city": { + "type": "string", + "example": "Dubai" + }, + "viewed_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-01T15:10:24.000000Z" + }, + "viewed_at_formatted": { + "type": "string", + "example": "2 minutes ago" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/workers/dashboard": { + "get": { + "tags": [ + "Worker/Dashboard" + ], + "summary": "Get Worker Consolidated Dashboard Data", + "description": "Allows authenticated workers to retrieve their dashboard metrics: active status, profile views count, currently working sponsor/employer details, and latest charity events/announcements.", + "responses": { + "200": { + "description": "Worker dashboard details retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "data": { + "type": "object", + "properties": { + "active_status": { + "type": "string", + "example": "active" + }, + "count_sponsors_viewed": { + "type": "integer", + "example": 5 + }, + "employer_contacted": { + "type": "integer", + "example": 2, + "description": "Unique count of employers who contacted this worker." + }, + "profile_viewed": { + "type": "integer", + "example": 3, + "description": "Unique count of employers who viewed this worker\u0027s profile." + }, + "currently_working_sponsor": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "integer", + "example": 2 + }, + "name": { + "type": "string", + "example": "Ahmad" + }, + "company_name": { + "type": "string", + "example": "Ahmad Tech Ltd" + }, + "nationality": { + "type": "string", + "example": "UAE" + }, + "city": { + "type": "string", + "example": "Dubai" + }, + "email": { + "type": "string", + "example": "ahmad@example.com" + }, + "phone": { + "type": "string", + "example": "+971509990001" + }, + "hired_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-01T15:10:24.000000Z" + }, + "hired_at_formatted": { + "type": "string", + "example": "Jun 01, 2026" + } + } + }, + "latest_charity_events_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "title": { + "type": "string", + "example": "Free Dental Checkup Camp" + }, + "body": { + "type": "string", + "example": "Emirates Charity is providing free screening." + }, + "type": { + "type": "string", + "example": "charity" + }, + "employer_name": { + "type": "string", + "example": "Emirates Charity" + }, + "company_name": { + "type": "string", + "example": "Emirates Charity Foundation" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-06-01T10:00:00.000000Z" + }, + "time_ago": { + "type": "string", + "example": "5 hours ago" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/employers/reviews": { + "get": { + "tags": [ + "Employer/Reviews" + ], + "summary": "Get Reviews Written by Employer", + "description": "Retrieves a paginated list of all reviews written by the authenticated employer.", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "description": "Page number.", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per_page", + "in": "query", + "required": false, + "description": "Number of items per page.", + "schema": { + "type": "integer", + "default": 15 + } + } + ], + "responses": { + "200": { + "description": "Reviews retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Employer/Reviews" + ], + "summary": "Add or Update Worker Review", + "description": "Allows employers to submit a review score and description comment for a worker. If the employer has already left a review, this will update it.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "worker_id", + "rating" + ], + "properties": { + "worker_id": { + "type": "integer", + "example": 136, + "description": "Worker ID being reviewed." + }, + "rating": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "example": 5, + "description": "Review score from 1 to 5." + }, + "comment": { + "type": "string", + "example": "Highly professional, punctual and did a great job!", + "description": "Optional detailed comment." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Review updated successfully." + }, + "201": { + "description": "Review added successfully." + }, + "422": { + "description": "Validation error." + } + } + } + }, + "/employers/reviews/{id}": { + "put": { + "tags": [ + "Employer/Reviews" + ], + "summary": "Edit Worker Review", + "description": "Allows employers to edit their previously created review score and comment by review ID.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Review ID reference.", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "rating": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "example": 4, + "description": "Updated rating score from 1 to 5." + }, + "comment": { + "type": "string", + "example": "Updated feedback comment.", + "description": "Updated review comment description." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Review edited successfully." + }, + "404": { + "description": "Review not found or unauthorized." + }, + "422": { + "description": "Validation error." + } + } + } + }, + "/employers/report": { + "post": { + "tags": [ + "Employer/Reviews" + ], + "summary": "Submit a Safety/Moderation Report (Employer)", + "description": "Submits a report on a chat conversation or a review. The report is logged in the safety queue and reviewed by administrators.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "type", + "item_id", + "reason" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Chat", + "Review" + ], + "example": "Chat", + "description": "The type of entity being reported (Chat or Review)." + }, + "item_id": { + "type": "string", + "example": "1", + "description": "The ID of the conversation or review being reported." + }, + "user_id": { + "type": "string", + "example": "5", + "description": "Optional ID of the user being reported." + }, + "reason": { + "type": "string", + "example": "Others", + "description": "The reason for submitting this report." + }, + "others": { + "type": "string", + "example": "Spam and malicious links", + "description": "Custom reason string. Used only if reason is \u0027Others\u0027." + }, + "description": { + "type": "string", + "example": "The worker requested payments off-platform and sent abusive messages.", + "description": "Optional detailed explanation/comments." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Report submitted successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Report submitted successfully. Our safety team will review it shortly." + }, + "report_id": { + "type": "string", + "example": "REP-ABCD1234" + } + } + } + } + } + }, + "404": { + "description": "Conversation or Review not found, or user is not authorized to report it." + }, + "422": { + "description": "Validation error." + } + } + } + }, + "/employers/report-reasons": { + "get": { + "tags": [ + "Employer/Reviews" + ], + "summary": "Get Safety Report Reasons (Employer)", + "description": "Returns a list of active safety/moderation report reasons. Can filter reasons by target type (Chat or Review).", + "parameters": [ + { + "name": "type", + "in": "query", + "required": false, + "description": "Optional type to filter reasons (Chat or Review). If not provided, returns all active reasons.", + "schema": { + "type": "string", + "enum": [ + "Chat", + "Review" + ] + } + } + ], + "responses": { + "200": { + "description": "List of report reasons retrieved successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "reasons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "reason": { + "type": "string", + "example": "Abusive language / Profanity" + }, + "type": { + "type": "string", + "example": "Both" + } + } + } + } + } + } + } + } + } + } + } + }, + "/employers/tickets": { + "get": { + "tags": [ + "Employer/Support" + ], + "summary": "List Support Tickets (Employer)", + "description": "Retrieves a paginated list of all support tickets created by the authenticated employer.", + "responses": { + "200": { + "description": "Tickets retrieved successfully." + } + } + }, + "post": { + "tags": [ + "Employer/Support" + ], + "summary": "Create Support Ticket (Employer)", + "description": "Creates a support ticket for an employer with a subject, description, and optional priority level.", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "subject", + "description" + ], + "properties": { + "subject": { + "type": "string", + "example": "Payment card rejected" + }, + "description": { + "type": "string", + "example": "My Visa card was declined when trying to buy the Premium Employer Pass." + }, + "priority": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "example": "medium" + }, + "reason_id": { + "type": "integer", + "example": 1, + "description": "Optional report/support reason ID." + }, + "voice_note": { + "type": "string", + "format": "binary", + "description": "Optional audio/voice note file attachment." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Ticket created successfully." + } + } + } + }, + "/employers/tickets/{id}": { + "get": { + "tags": [ + "Employer/Support" + ], + "summary": "Get Support Ticket Details \u0026 Replies (Employer)", + "description": "Retrieves the details of a support ticket and its chronological reply history.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Ticket details and replies retrieved successfully." + } + } + } + }, + "/employers/tickets/{id}/reply": { + "post": { + "tags": [ + "Employer/Support" + ], + "summary": "Reply to Support Ticket (Employer)", + "description": "Submits a text reply or message to an active, open, or resolved support ticket.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "I tried a different card and it still fails." + }, + "voice_note": { + "type": "string", + "format": "binary", + "description": "Optional audio/voice note file attachment." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Reply posted successfully." + } + } + } }, - "type": { - "type": "string", - "example": "Both" + "/workers/forgot-password": { + "post": { + "tags": [ + "Workers - Auth" + ], + "summary": "Forgot Password (Send OTP)", + "description": "Sends a 6-digit OTP to verify the worker\u0027s identity using their registered mobile number. Workers do not use email — mobile/phone is the primary identifier. OTP is valid for 10 minutes.", + "operationId": "workerForgotPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971501234567", + "description": "Worker\u0027s registered mobile number" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OTP sent successfully (response is identical whether account exists or not, to prevent enumeration)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "If an account with that mobile number exists, a reset OTP has been sent." + } + } + } + } + } + }, + "422": { + "description": "Validation error — phone is required" + } + } + } + }, + "/workers/reset-password": { + "post": { + "tags": [ + "Workers - Auth" + ], + "summary": "Reset Password (Verify OTP)", + "description": "Verifies the OTP sent to the worker\u0027s mobile number and sets a new password. The phone number must match the one used in the forgot-password step.", + "operationId": "workerResetPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "phone", + "otp", + "password", + "password_confirmation" + ], + "properties": { + "phone": { + "type": "string", + "example": "+971501234567", + "description": "Worker\u0027s registered mobile number" + }, + "otp": { + "type": "string", + "example": "482910", + "description": "6-digit OTP received" + }, + "password": { + "type": "string", + "example": "newpassword123", + "description": "New password (min 8 chars)" + }, + "password_confirmation": { + "type": "string", + "example": "newpassword123", + "description": "Must match password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password reset successfully" + }, + "422": { + "description": "Invalid OTP, expired OTP, or validation error" + }, + "404": { + "description": "Account not found" + } + } + } + }, + "/employers/forgot-password": { + "post": { + "tags": [ + "Employers - Auth" + ], + "summary": "Forgot Password (Send OTP)", + "description": "Sends a 6-digit OTP to the employer\u0027s registered email address. OTP expires in 10 minutes.", + "operationId": "employerForgotPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "employer@example.com" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OTP sent (response identical whether account exists or not)" + }, + "422": { + "description": "Validation error" + } + } + } + }, + "/employers/reset-password": { + "post": { + "tags": [ + "Employers - Auth" + ], + "summary": "Reset Password (Verify OTP)", + "description": "Verifies the OTP sent to the employer\u0027s email and sets a new password.", + "operationId": "employerResetPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "otp", + "password", + "password_confirmation" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "employer@example.com" + }, + "otp": { + "type": "string", + "example": "482910" + }, + "password": { + "type": "string", + "example": "newpassword123" + }, + "password_confirmation": { + "type": "string", + "example": "newpassword123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password reset successfully" + }, + "422": { + "description": "Invalid or expired OTP" + } + } + } + }, + "/sponsors/forgot-password": { + "post": { + "tags": [ + "Sponsors - Auth" + ], + "summary": "Forgot Password (Send OTP)", + "description": "Sends a 6-digit OTP to the sponsor\u0027s registered email. Accepts email or mobile number as identifier. OTP expires in 10 minutes.", + "operationId": "sponsorForgotPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "sponsor@example.com", + "description": "Email address (either email or mobile required)" + }, + "mobile": { + "type": "string", + "example": "+971501234567", + "description": "Mobile number (either email or mobile required)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OTP sent (response identical whether account exists or not)" + }, + "422": { + "description": "Validation error — email or mobile required" + } + } + } + }, + "/sponsors/reset-password": { + "post": { + "tags": [ + "Sponsors - Auth" + ], + "summary": "Reset Password (Verify OTP)", + "description": "Verifies the OTP sent to the sponsor\u0027s email and sets a new password. Use the email field to identify the account.", + "operationId": "sponsorResetPassword", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "otp", + "password", + "password_confirmation" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "sponsor@example.com" + }, + "otp": { + "type": "string", + "example": "482910" + }, + "password": { + "type": "string", + "example": "newpassword123" + }, + "password_confirmation": { + "type": "string", + "example": "newpassword123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password reset successfully" + }, + "422": { + "description": "Invalid or expired OTP" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "Sponsor": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "full_name": { + "type": "string", + "example": "Mohammed Al-Rashidi" + }, + "organization_name": { + "type": "string", + "example": "Al-Rashidi Charitable Foundation" + }, + "email": { + "type": "string", + "example": "sponsor.971501112233@migrant.ae" + }, + "mobile": { + "type": "string", + "example": "+971501112233" + }, + "nationality": { + "type": "string", + "example": "UAE" + }, + "city": { + "type": "string", + "example": "Dubai" + }, + "address": { + "type": "string", + "example": "Villa 12, Jumeirah 2" + }, + "country_code": { + "type": "string", + "example": "+971" + }, + "is_verified": { + "type": "boolean", + "example": false + }, + "status": { + "type": "string", + "example": "active" + }, + "license_file": { + "type": "string", + "example": "uploads/licenses/1234567890_license_trade.pdf" + }, + "emirates_id_file": { + "type": "string", + "example": "uploads/licenses/1234567890_emirates_id.pdf" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "Worker": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 136 + }, + "name": { + "type": "string", + "example": "Amina Al-Masry" + }, + "email": { + "type": "string", + "example": "worker.971509998888@migrant.com" + }, + "phone": { + "type": "string", + "example": "+971509998888" + }, + "language": { + "type": "string", + "example": "HI" + }, + "fcm_token": { + "type": "string", + "example": "fcm_token_example" + }, + "nationality": { + "type": "string", + "example": "Egypt" + }, + "age": { + "type": "integer", + "example": 28 + }, + "salary": { + "type": "string", + "example": "1500.00" + }, + "availability": { + "type": "string", + "example": "Immediate" + }, + "experience": { + "type": "string", + "example": "4 Years" + }, + "religion": { + "type": "string", + "example": "Muslim" + }, + "bio": { + "type": "string", + "example": "Certified infant caregiver and housekeeper with excellent cooking skills." + }, + "verified": { + "type": "boolean", + "example": false + }, + "status": { + "type": "string", + "example": "active" + }, + "in_country": { + "type": "boolean", + "example": true + }, + "visa_status": { + "type": "string", + "example": "Tourist Visa" + }, + "passport_status": { + "type": "string", + "example": "Passport Verified" + }, + "emirates_id_status": { + "type": "string", + "example": "Passport Verified" + }, + "preferred_job_type": { + "type": "string", + "example": "full-time" + }, + "live_in_out": { + "type": "string", + "example": "Live-out" + }, + "gender": { + "type": "string", + "enum": [ + "male", + "female", + "other" + ], + "example": "male" + }, + "preferred_location": { + "type": "string", + "example": "Dubai Marina" + }, + "country": { + "type": "string", + "example": "UAE" + }, + "city": { + "type": "string", + "example": "Dubai" + }, + "area": { + "type": "string", + "example": "Marina" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-05-20T14:54:26.000000Z" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "example": "2026-05-20T14:54:26.000000Z" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 7 + }, + "name": { + "type": "string", + "example": "General Helper" + } + } + }, + "skills": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 6 + }, + "name": { + "type": "string", + "example": "Cleaning" + } + } + } + }, + "documents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkerDocument" + } + } + } + }, + "WorkerDocument": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 15 + }, + "worker_id": { + "type": "integer", + "example": 136 + }, + "type": { + "type": "string", + "example": "passport" + }, + "number": { + "type": "string", + "example": "P345892" + }, + "issue_date": { + "type": "string", + "format": "date", + "example": "2024-05-20" + }, + "expiry_date": { + "type": "string", + "format": "date", + "example": "2034-05-20" + }, + "ocr_accuracy": { + "type": "number", + "example": 98.5 + }, + "file_path": { + "type": "string", + "example": "uploads/documents/1716200000_passport_my_file.jpg" + } + } + }, + "JobOffer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "employer_id": { + "type": "integer", + "example": 2 + }, + "worker_id": { + "type": "integer", + "example": 136 + }, + "work_date": { + "type": "string", + "format": "date", + "example": "2026-06-01" + }, + "location": { + "type": "string", + "example": "Dubai Marina, Silverene Towers" + }, + "salary": { + "type": "string", + "example": "2500.00" + }, + "notes": { + "type": "string", + "example": "Require housekeeping support starting from June 1st." + }, + "status": { + "type": "string", + "example": "pending" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-05-20T15:23:44.000000Z" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "example": "2026-05-20T15:23:44.000000Z" + } + } } - } - } - } - } - } - } - } - } - } - } - }, - "/employers/tickets": { - "get": { - "tags": [ - "Employer/Support" - ], - "summary": "List Support Tickets (Employer)", - "description": "Retrieves a paginated list of all support tickets created by the authenticated employer.", - "responses": { - "200": { - "description": "Tickets retrieved successfully." - } - } - }, - "post": { - "tags": [ - "Employer/Support" - ], - "summary": "Create Support Ticket (Employer)", - "description": "Creates a support ticket for an employer with a subject, description, and optional priority level.", - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "required": [ - "subject", - "description" - ], - "properties": { - "subject": { - "type": "string", - "example": "Payment card rejected" - }, - "description": { - "type": "string", - "example": "My Visa card was declined when trying to buy the Premium Employer Pass." - }, - "priority": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ], - "example": "medium" - }, - "reason_id": { - "type": "integer", - "example": 1, - "description": "Optional report/support reason ID." - }, - "voice_note": { - "type": "string", - "format": "binary", - "description": "Optional audio/voice note file attachment." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Ticket created successfully." - } - } - } - }, - "/employers/tickets/{id}": { - "get": { - "tags": [ - "Employer/Support" - ], - "summary": "Get Support Ticket Details & Replies (Employer)", - "description": "Retrieves the details of a support ticket and its chronological reply history.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Ticket details and replies retrieved successfully." - } - } - } - }, - "/employers/tickets/{id}/reply": { - "post": { - "tags": [ - "Employer/Support" - ], - "summary": "Reply to Support Ticket (Employer)", - "description": "Submits a text reply or message to an active, open, or resolved support ticket.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "I tried a different card and it still fails." - }, - "voice_note": { - "type": "string", - "format": "binary", - "description": "Optional audio/voice note file attachment." - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Reply posted successfully." - } - } - } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "Sponsor": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "full_name": { - "type": "string", - "example": "Mohammed Al-Rashidi" - }, - "organization_name": { - "type": "string", - "example": "Al-Rashidi Charitable Foundation" - }, - "email": { - "type": "string", - "example": "sponsor.971501112233@migrant.ae" - }, - "mobile": { - "type": "string", - "example": "+971501112233" - }, - "nationality": { - "type": "string", - "example": "UAE" - }, - "city": { - "type": "string", - "example": "Dubai" - }, - "address": { - "type": "string", - "example": "Villa 12, Jumeirah 2" - }, - "country_code": { - "type": "string", - "example": "+971" - }, - "is_verified": { - "type": "boolean", - "example": false - }, - "status": { - "type": "string", - "example": "active" - }, - "license_file": { - "type": "string", - "example": "uploads/licenses/1234567890_license_trade.pdf" - }, - "emirates_id_file": { - "type": "string", - "example": "uploads/licenses/1234567890_emirates_id.pdf" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example" - }, - "created_at": { - "type": "string", - "format": "date-time" - } - } - }, - "Worker": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 136 - }, - "name": { - "type": "string", - "example": "Amina Al-Masry" - }, - "email": { - "type": "string", - "example": "worker.971509998888@migrant.com" - }, - "phone": { - "type": "string", - "example": "+971509998888" - }, - "language": { - "type": "string", - "example": "HI" - }, - "fcm_token": { - "type": "string", - "example": "fcm_token_example" - }, - "nationality": { - "type": "string", - "example": "Egypt" - }, - "age": { - "type": "integer", - "example": 28 - }, - "salary": { - "type": "string", - "example": "1500.00" - }, - "availability": { - "type": "string", - "example": "Immediate" - }, - "experience": { - "type": "string", - "example": "4 Years" - }, - "religion": { - "type": "string", - "example": "Muslim" - }, - "bio": { - "type": "string", - "example": "Certified infant caregiver and housekeeper with excellent cooking skills." - }, - "verified": { - "type": "boolean", - "example": false - }, - "status": { - "type": "string", - "example": "active" - }, - "in_country": { - "type": "boolean", - "example": true - }, - "visa_status": { - "type": "string", - "example": "Tourist Visa" - }, - "passport_status": { - "type": "string", - "example": "Passport Verified" - }, - "emirates_id_status": { - "type": "string", - "example": "Passport Verified" - }, - "preferred_job_type": { - "type": "string", - "example": "full-time" - }, - "live_in_out": { - "type": "string", - "example": "Live-out" - }, - "gender": { - "type": "string", - "enum": [ - "male", - "female", - "other" - ], - "example": "male" - }, - "preferred_location": { - "type": "string", - "example": "Dubai Marina" - }, - "country": { - "type": "string", - "example": "UAE" - }, - "city": { - "type": "string", - "example": "Dubai" - }, - "area": { - "type": "string", - "example": "Marina" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2026-05-20T14:54:26.000000Z" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2026-05-20T14:54:26.000000Z" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 7 - }, - "name": { - "type": "string", - "example": "General Helper" - } - } - }, - "skills": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 6 - }, - "name": { - "type": "string", - "example": "Cleaning" - } - } - } - }, - "documents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/WorkerDocument" - } - } - } - }, - "WorkerDocument": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 15 - }, - "worker_id": { - "type": "integer", - "example": 136 - }, - "type": { - "type": "string", - "example": "passport" - }, - "number": { - "type": "string", - "example": "P345892" - }, - "issue_date": { - "type": "string", - "format": "date", - "example": "2024-05-20" - }, - "expiry_date": { - "type": "string", - "format": "date", - "example": "2034-05-20" - }, - "ocr_accuracy": { - "type": "number", - "example": 98.5 - }, - "file_path": { - "type": "string", - "example": "uploads/documents/1716200000_passport_my_file.jpg" - } - } - }, - "JobOffer": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "example": 1 - }, - "employer_id": { - "type": "integer", - "example": 2 - }, - "worker_id": { - "type": "integer", - "example": 136 - }, - "work_date": { - "type": "string", - "format": "date", - "example": "2026-06-01" - }, - "location": { - "type": "string", - "example": "Dubai Marina, Silverene Towers" - }, - "salary": { - "type": "string", - "example": "2500.00" - }, - "notes": { - "type": "string", - "example": "Require housekeeping support starting from June 1st." - }, - "status": { - "type": "string", - "example": "pending" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2026-05-20T15:23:44.000000Z" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2026-05-20T15:23:44.000000Z" - } - } - } - } - } -} \ No newline at end of file + } + } +} diff --git a/resources/js/Pages/Employer/Auth/Register.jsx b/resources/js/Pages/Employer/Auth/Register.jsx index 2ddf126..e2591e7 100644 --- a/resources/js/Pages/Employer/Auth/Register.jsx +++ b/resources/js/Pages/Employer/Auth/Register.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Head, Link, router } from '@inertiajs/react'; import axios from 'axios'; import { toast } from 'sonner'; @@ -6,14 +6,131 @@ import { Loader2, Users, DollarSign, - MessageSquare + MessageSquare, + ChevronDown, + Search } from 'lucide-react'; +// Common country dial codes with flag emojis +const COUNTRY_CODES = [ + { code: 'AE', dial: '+971', flag: '🇦🇪', name: 'United Arab Emirates' }, + { code: 'SA', dial: '+966', flag: '🇸🇦', name: 'Saudi Arabia' }, + { code: 'IN', dial: '+91', flag: '🇮🇳', name: 'India' }, + { code: 'PK', dial: '+92', flag: '🇵🇰', name: 'Pakistan' }, + { code: 'PH', dial: '+63', flag: '🇵🇭', name: 'Philippines' }, + { code: 'BD', dial: '+880', flag: '🇧🇩', name: 'Bangladesh' }, + { code: 'LK', dial: '+94', flag: '🇱🇰', name: 'Sri Lanka' }, + { code: 'NP', dial: '+977', flag: '🇳🇵', name: 'Nepal' }, + { code: 'EG', dial: '+20', flag: '🇪🇬', name: 'Egypt' }, + { code: 'JO', dial: '+962', flag: '🇯🇴', name: 'Jordan' }, + { code: 'LB', dial: '+961', flag: '🇱🇧', name: 'Lebanon' }, + { code: 'IQ', dial: '+964', flag: '🇮🇶', name: 'Iraq' }, + { code: 'KW', dial: '+965', flag: '🇰🇼', name: 'Kuwait' }, + { code: 'QA', dial: '+974', flag: '🇶🇦', name: 'Qatar' }, + { code: 'BH', dial: '+973', flag: '🇧🇭', name: 'Bahrain' }, + { code: 'OM', dial: '+968', flag: '🇴🇲', name: 'Oman' }, + { code: 'ET', dial: '+251', flag: '🇪🇹', name: 'Ethiopia' }, + { code: 'KE', dial: '+254', flag: '🇰🇪', name: 'Kenya' }, + { code: 'UG', dial: '+256', flag: '🇺🇬', name: 'Uganda' }, + { code: 'GH', dial: '+233', flag: '🇬🇭', name: 'Ghana' }, + { code: 'NG', dial: '+234', flag: '🇳🇬', name: 'Nigeria' }, + { code: 'ID', dial: '+62', flag: '🇮🇩', name: 'Indonesia' }, + { code: 'MY', dial: '+60', flag: '🇲🇾', name: 'Malaysia' }, + { code: 'GB', dial: '+44', flag: '🇬🇧', name: 'United Kingdom' }, + { code: 'US', dial: '+1', flag: '🇺🇸', name: 'United States' }, +]; + +function CountryCodeSelector({ value, onChange, hasError }) { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(''); + const ref = useRef(null); + const searchRef = useRef(null); + + const selected = COUNTRY_CODES.find(c => c.dial === value) || COUNTRY_CODES[0]; + + const filtered = COUNTRY_CODES.filter(c => + c.name.toLowerCase().includes(search.toLowerCase()) || + c.dial.includes(search) + ); + + useEffect(() => { + const handler = (e) => { + if (ref.current && !ref.current.contains(e.target)) setOpen(false); + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, []); + + useEffect(() => { + if (open && searchRef.current) searchRef.current.focus(); + }, [open]); + + return ( +
+ + + {open && ( +
+ {/* Search */} +
+
+ + setSearch(e.target.value)} + placeholder="Search country..." + className="flex-1 bg-transparent text-xs outline-none text-slate-700 placeholder-slate-400" + /> +
+
+ + {/* List */} +
    + {filtered.length === 0 ? ( +
  • No results found
  • + ) : filtered.map(c => ( +
  • + +
  • + ))} +
+
+ )} +
+ ); +} + export default function Register() { + const [countryCode, setCountryCode] = useState('+971'); const [data, setData] = useState({ name: '', email: '', phone: '', + address: '', }); const [loading, setLoading] = useState(false); @@ -41,8 +158,12 @@ export default function Register() { if (!data.phone.trim()) { newErrors.phone = 'Mobile number is required.'; - } else if (!/^[0-9]{7,15}$/.test(data.phone)) { - newErrors.phone = 'Please enter a valid mobile number (7-15 digits without country code).'; + } else if (!/^[0-9]{4,14}$/.test(data.phone.replace(/^0+/, ''))) { + newErrors.phone = 'Please enter a valid mobile number (digits only, without country code).'; + } + + if (!data.address.trim()) { + newErrors.address = 'Address is required.'; } setErrors(newErrors); @@ -63,7 +184,9 @@ export default function Register() { const formData = new FormData(); formData.append('name', data.name); formData.append('email', data.email); - formData.append('phone', data.phone); + formData.append('phone', countryCode + data.phone.replace(/^0+/, '')); + formData.append('country_code', countryCode); + formData.append('address', data.address); try { await axios.post('/employer/register', formData, { @@ -239,20 +362,48 @@ export default function Register() { {/* Mobile Number */}
- handleInputChange('phone', e.target.value)} - placeholder="e.g. 501234567" +
+ + handleInputChange('phone', e.target.value.replace(/[^0-9]/g, ''))} + placeholder="501234567" + className={`flex-1 px-3.5 py-3 rounded-r-xl border text-sm focus:outline-none focus:ring-2 transition-all ${ + errors.phone + ? 'border-red-500 focus:ring-red-500/20 focus:border-red-500' + : 'border-slate-300 focus:ring-[#185FA5]/20 focus:border-[#185FA5]' + }`} + /> +
+ {errors.phone && ( +

+ {Array.isArray(errors.phone) ? errors.phone[0] : errors.phone} +

+ )} +
+ + {/* Address */} +
+ +