all(), [ 'full_name' => 'required|string|max:255', 'mobile' => 'required|string|max:50|unique:sponsors,mobile|unique:employer_profiles,phone', 'password' => 'required|string|min:6', 'license_file' => 'nullable|file|mimes:jpeg,png,jpg,pdf|max:10240', 'emirates_id_file' => 'nullable|file|mimes:jpeg,png,jpg,pdf|max:10240', 'organization_name' => 'required|string|max:255', 'email' => 'required|email|max:255|unique:sponsors,email|unique:users,email', 'nationality' => 'required|string|max:100', 'city' => 'required|string|max:100', 'address' => 'required|string|max:255', 'country_code' => 'required|string|max:10', 'license_expiry' => 'nullable|date_format:Y-m-d', 'fcm_token' => 'nullable|string|max:255', ], [ 'mobile.unique' => 'This mobile number is already registered.', 'email.unique' => 'This email address is already registered.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { // Auto-generate email if not provided $mobileClean = preg_replace('/[^0-9]/', '', $request->mobile); $email = $request->email ?? "sponsor.{$mobileClean}@migrant.ae"; if (!$request->email && Sponsor::where('email', $email)->exists()) { $email = "sponsor.{$mobileClean}." . rand(10, 99) . "@migrant.ae"; } $licenseExpiry = $request->license_expiry; if ($request->hasFile('license_file')) { $licenseFile = $request->file('license_file'); $ocrLicense = \App\Services\OcrDocumentService::extractSponsorLicenseData($licenseFile); $licenseExpiry = $licenseExpiry ?? $ocrLicense['expiry_date']; } $emiratesIdPath = null; if ($request->hasFile('emirates_id_file')) { $emiratesIdFile = $request->file('emirates_id_file'); \App\Services\OcrDocumentService::extractEmiratesIdFrontData($emiratesIdFile); } $licensePath = null; $apiToken = Str::random(80); $sponsor = DB::transaction(function () use ($request, $email, $licensePath, $emiratesIdPath, $apiToken, $licenseExpiry) { return Sponsor::create([ 'full_name' => $request->full_name, 'organization_name' => $request->organization_name, 'email' => $email, 'mobile' => $request->mobile, 'password' => Hash::make($request->password), 'country_code' => $request->country_code, 'nationality' => $request->nationality, 'city' => $request->city, 'address' => $request->address, 'license_file' => $licensePath, 'emirates_id_file' => $emiratesIdPath, 'license_expiry' => $licenseExpiry, 'status' => 'active', 'is_verified' => false, 'subscription_status' => 'none', 'api_token' => $apiToken, 'fcm_token' => $request->fcm_token, ]); }); if ($request->filled('fcm_token')) { \App\Services\FCMService::sendPushNotification( $request->fcm_token, 'Welcome to Migrant', 'Sponsor account registered successfully. Your license is pending admin review.' ); } return response()->json([ 'success' => true, 'message' => 'Sponsor account registered successfully. Your license is pending admin review.', 'data' => [ 'sponsor' => $sponsor->makeHidden(['password', 'api_token']), 'token' => $apiToken, ] ], 201); } catch (\Exception $e) { logger()->error('Sponsor Registration Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred during registration. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Authenticate a Sponsor and return their bearer token. * * POST /api/sponsors/login */ public function login(Request $request) { $validator = Validator::make($request->all(), [ 'mobile' => 'required|string', 'password' => 'required|string', 'fcm_token' => 'nullable|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } $sponsor = Sponsor::where('mobile', $request->mobile)->first(); if (!$sponsor || !Hash::check($request->password, $sponsor->password)) { return response()->json([ 'success' => false, 'message' => 'Invalid mobile number or password.' ], 401); } if ($sponsor->status === 'suspended') { return response()->json([ 'success' => false, 'message' => 'Your account has been suspended. Please contact support.' ], 403); } // Rotate token on each login for security $apiToken = Str::random(80); $updateData = [ 'api_token' => $apiToken, 'last_login_at' => now(), ]; if ($request->has('fcm_token')) { $updateData['fcm_token'] = $request->fcm_token; } $sponsor->update($updateData); if ($request->filled('fcm_token')) { \App\Services\FCMService::sendPushNotification( $request->fcm_token, 'Successful Login', 'Welcome back to your Migrant sponsor account.' ); } return response()->json([ 'success' => true, 'message' => 'Login successful.', 'data' => [ 'sponsor' => $sponsor->makeHidden(['password', 'api_token']), 'token' => $apiToken, ] ], 200); } /** * Upload Sponsor's Emirates ID Front. * POST /api/sponsors/register/emirates-front */ public function uploadEmiratesIdFront(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'emirates_id_front' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', ], [ 'email.required' => 'The email address is required.', 'emirates_id_front.required' => 'Please upload the front side of your Emirates ID.', 'emirates_id_front.mimes' => 'The Emirates ID front must be an image or a PDF.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $sponsor = Sponsor::where('email', $request->email)->first(); if (!$sponsor) { return response()->json([ 'success' => false, 'message' => 'Sponsor account not found.' ], 404); } $frontFile = $request->file('emirates_id_front'); $frontOcr = \App\Services\OcrDocumentService::extractEmiratesIdFrontData($frontFile); $sponsor->update([ 'emirates_id_file' => null, ]); return response()->json([ 'success' => true, 'message' => 'Emirates ID front uploaded and processed successfully.', 'ocr_extracted_data' => $frontOcr, 'email' => $request->email, ], 200); } catch (\Exception $e) { logger()->error('API Sponsor Emirates ID Front Upload Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while uploading the Emirates ID front.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Upload Sponsor's Emirates ID Back. * POST /api/sponsors/register/emirates-back */ public function uploadEmiratesIdBack(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'emirates_id_back' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', ], [ 'email.required' => 'The email address is required.', 'emirates_id_back.required' => 'Please upload the back side of your Emirates ID.', 'emirates_id_back.mimes' => 'The Emirates ID back must be an image or a PDF.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $sponsor = Sponsor::where('email', $request->email)->first(); if (!$sponsor) { return response()->json([ 'success' => false, 'message' => 'Sponsor account not found.' ], 404); } $backFile = $request->file('emirates_id_back'); $backOcr = \App\Services\OcrDocumentService::extractEmiratesIdBackData($backFile); $sponsor->update([ 'emirates_id_file' => null, ]); return response()->json([ 'success' => true, 'message' => 'Emirates ID back uploaded and processed successfully.', 'ocr_extracted_data' => $backOcr, 'email' => $request->email, ], 200); } catch (\Exception $e) { logger()->error('API Sponsor Emirates ID Back Upload Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while uploading the Emirates ID back.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Upload Sponsor's License document. * POST /api/sponsors/register/license */ public function uploadLicense(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'license_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', ], [ 'email.required' => 'The email address is required.', 'license_file.required' => 'Please upload the organization or trade license.', 'license_file.mimes' => 'The license file must be an image or a PDF.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $sponsor = Sponsor::where('email', $request->email)->first(); if (!$sponsor) { return response()->json([ 'success' => false, 'message' => 'Sponsor account not found.' ], 404); } $licenseFile = $request->file('license_file'); $ocrLicense = \App\Services\OcrDocumentService::extractSponsorLicenseData($licenseFile); $extractedExpiry = $ocrLicense['expiry_date']; $sponsor->update([ 'license_file' => null, 'license_expiry' => $extractedExpiry, ]); return response()->json([ 'success' => true, 'message' => 'License uploaded and processed successfully.', 'ocr_extracted_data' => $ocrLicense, 'email' => $request->email, ], 200); } catch (\Exception $e) { logger()->error('API Sponsor License Upload Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while uploading the license.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } }