all(), [ 'name' => 'required|string|max:255', 'phone' => 'required|string|max:50|unique:workers,phone', 'salary' => 'required|numeric|min:0', 'password' => 'required|string|min:6', // Password is required 'passport_file' => 'required|file|mimes:jpeg,png,jpg,pdf|max:10240', // STRICTLY REQUIRED (Max 10MB) 'skills' => 'nullable', // Handled dynamically in controller (JSON, Array, or Comma-separated) 'visa_file' => 'nullable|file|mimes:jpeg,png,jpg,pdf|max:10240', // Optional (Max 10MB) ], [ 'phone.unique' => 'This mobile number is already registered.', 'password.min' => 'The password must be at least 6 characters long.', 'passport_file.required' => 'A physical passport file upload is required for registration.', 'passport_file.mimes' => 'The passport document must be an image (jpg, jpeg, png) or a PDF.', 'passport_file.max' => 'The passport document must not exceed 10MB.', 'visa_file.mimes' => 'The visa document must be an image (jpg, jpeg, png) or a PDF.', 'visa_file.max' => 'The visa document must not exceed 10MB.', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { // 2. Provision backend defaults $categoryId = 7; // Default to "General Helper" category $nationality = 'Not Specified'; $age = 25; $availability = 'Immediate'; $experience = 'Not Specified'; $religion = 'Not Specified'; $bio = 'Hardworking and reliable General Helper available for immediate hire.'; // Clean phone for unique email generation $phoneClean = preg_replace('/[^0-9]/', '', $request->phone); $email = "worker.{$phoneClean}@migrant.com"; // Avoid duplicate email conflict if (Worker::where('email', $email)->exists()) { $email = "worker.{$phoneClean}." . rand(10, 99) . "@migrant.com"; } // Parse skills robustly for multipart form variants $skillsArray = []; if ($request->has('skills')) { $skillsInput = $request->skills; if (is_array($skillsInput)) { $skillsArray = $skillsInput; } elseif (is_string($skillsInput)) { $decoded = json_decode($skillsInput, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $skillsArray = $decoded; } else { // Fallback: comma separated e.g. "6,8" $skillsArray = array_filter(array_map('trim', explode(',', $skillsInput))); } } } // Handle file uploads securely $passportPath = null; $visaPath = null; // Ensure destination directory exists $destinationPath = public_path('uploads/documents'); if (!file_exists($destinationPath)) { mkdir($destinationPath, 0755, true); } if ($request->hasFile('passport_file')) { $file = $request->file('passport_file'); $fileName = time() . '_passport_' . preg_replace('/[^a-zA-Z0-9_.-]/', '', $file->getClientOriginalName()); $file->move($destinationPath, $fileName); $passportPath = 'uploads/documents/' . $fileName; } if ($request->hasFile('visa_file')) { $file = $request->file('visa_file'); $fileName = time() . '_visa_' . preg_replace('/[^a-zA-Z0-9_.-]/', '', $file->getClientOriginalName()); $file->move($destinationPath, $fileName); $visaPath = 'uploads/documents/' . $fileName; } // Generate initial API token $apiToken = Str::random(80); // 3. Save Worker and Document records inside database transaction $result = DB::transaction(function () use ($request, $categoryId, $email, $nationality, $age, $availability, $experience, $religion, $bio, $skillsArray, $passportPath, $visaPath, $apiToken) { $worker = Worker::create([ 'name' => $request->name, 'email' => $email, 'phone' => $request->phone, 'password' => $request->password, // Will cast to hashed auto-magically 'nationality' => $nationality, 'age' => $age, 'salary' => $request->salary, 'availability' => $availability, 'experience' => $experience, 'religion' => $religion, 'bio' => $bio, 'category_id' => $categoryId, 'verified' => false, 'status' => 'active', 'api_token' => $apiToken, ]); // Sync selected skills if (!empty($skillsArray)) { $worker->skills()->sync($skillsArray); } // Provision Passport document record (User uploaded REQUIRED file) $worker->documents()->create([ 'type' => 'passport', 'number' => 'P' . rand(100000, 999999), 'issue_date' => now()->subYears(2)->toDateString(), 'expiry_date' => now()->addYears(8)->toDateString(), 'ocr_accuracy' => 98.5, 'file_path' => $passportPath, ]); // Provision Visa document record (User uploaded OR mock scaffolding) $worker->documents()->create([ 'type' => 'visa', 'number' => 'V' . rand(100000, 999999), 'issue_date' => now()->subYear()->toDateString(), 'expiry_date' => now()->addYears(2)->toDateString(), 'ocr_accuracy' => $visaPath ? 98.5 : 0.0, 'file_path' => $visaPath, ]); return $worker; }); // Load relations for response $result->load(['category', 'skills', 'documents']); return response()->json([ 'success' => true, 'message' => 'Worker registered and authenticated successfully.', 'data' => [ 'worker' => $result, 'token' => $apiToken ] ], 201); } catch (\Exception $e) { logger()->error('Mobile Unified Worker Registration Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred during worker registration. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Authenticate a worker and return a secure Bearer token. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function login(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required|string', 'password' => 'required|string', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $worker = Worker::where('phone', $request->phone)->first(); if (!$worker || !Hash::check($request->password, $worker->password)) { return response()->json([ 'success' => false, 'message' => 'Invalid mobile number or password.' ], 401); } // Generate and assign a fresh API token $apiToken = Str::random(80); $worker->update(['api_token' => $apiToken]); return response()->json([ 'success' => true, 'message' => 'Worker logged in successfully.', 'data' => [ 'worker' => $worker->load(['category', 'skills', 'documents']), 'token' => $apiToken ] ], 200); } catch (\Exception $e) { logger()->error('Mobile Worker Login Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred during login. Please try again.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } }