migrant-web/app/Http/Controllers/Api/WorkerAuthController.php
2026-05-21 10:39:23 +05:30

245 lines
9.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Worker;
use App\Models\WorkerCategory;
use App\Models\WorkerDocument;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class WorkerAuthController extends Controller
{
/**
* Register a new worker via Mobile App API (Single Unified Multipart Request).
*
* Accepts: name, phone, salary, password, skills, passport_file (REQUIRED), and visa_file.
* All other fields are generated with backend defaults.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function register(Request $request)
{
// 1. Validate the unified request
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'phone' => 'required|string|max:50|unique:workers,phone',
'salary' => 'required|numeric|min:0',
'password' => 'required|string|min:6', // 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);
}
}
}