490 lines
21 KiB
PHP
490 lines
21 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Sponsor;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SponsorAuthController extends Controller
|
|
{
|
|
/**
|
|
* Register a new Sponsor account.
|
|
*
|
|
* Required fields: full_name, mobile, password, license_file
|
|
* Optional: organization_name, email, nationality, city, address, country_code
|
|
*
|
|
* POST /api/sponsors/register
|
|
*/
|
|
public function register(Request $request)
|
|
{
|
|
$validator = Validator::make($request->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' => 'nullable|array',
|
|
'emirates_id' => 'nullable|array',
|
|
'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.',
|
|
]);
|
|
|
|
$validator->after(function ($validator) use ($request) {
|
|
$emiratesIdInput = $request->input('emirates_id');
|
|
if (is_array($emiratesIdInput) && empty($emiratesIdInput['name'])) {
|
|
$validator->errors()->add('emirates_id.name', 'Failed to extract name from the Emirates ID. Please upload a clearer document.');
|
|
}
|
|
});
|
|
|
|
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;
|
|
$licenseInput = $request->input('license');
|
|
$licenseNumber = null;
|
|
if (is_array($licenseInput)) {
|
|
$rawExpiry = $licenseInput['expiry_date'] ?? $licenseInput['license_expiry'] ?? null;
|
|
if ($rawExpiry) {
|
|
$licenseExpiry = $licenseExpiry ?? \App\Services\OcrDocumentService::normaliseDate($rawExpiry);
|
|
}
|
|
$licenseNumber = $licenseInput['license_number'] ?? $licenseInput['license_no'] ?? $licenseInput['document_number'] ?? null;
|
|
}
|
|
|
|
$emiratesIdInput = $request->input('emirates_id');
|
|
$emiratesId = null;
|
|
$emiratesIdExpiry = null;
|
|
$emiratesIdName = null;
|
|
$emiratesIdDob = null;
|
|
$emiratesIdNationality = null;
|
|
$emiratesIdIssueDate = null;
|
|
$emiratesIdEmployer = null;
|
|
$emiratesIdIssuePlace = null;
|
|
$emiratesIdOccupation = null;
|
|
|
|
if (is_array($emiratesIdInput)) {
|
|
$emiratesId = $emiratesIdInput['emirates_id_number'] ?? $emiratesIdInput['id_number'] ?? $emiratesIdInput['id number'] ?? $emiratesIdInput['card_number'] ?? null;
|
|
$emiratesIdExpiry = $emiratesIdInput['expiry_date'] ?? $emiratesIdInput['expiry date'] ?? $emiratesIdInput['emirates_id_expiry'] ?? null;
|
|
$emiratesIdName = $emiratesIdInput['name'] ?? null;
|
|
if ($emiratesIdName) {
|
|
$request->merge(['full_name' => $emiratesIdName]);
|
|
}
|
|
$emiratesIdDob = $emiratesIdInput['date_of_birth'] ?? $emiratesIdInput['date of birth'] ?? $emiratesIdInput['dob'] ?? null;
|
|
$emiratesIdNationality = $emiratesIdInput['nationality'] ?? null;
|
|
$emiratesIdGender = $emiratesIdInput['gender'] ?? $emiratesIdInput['sex'] ?? null;
|
|
$emiratesIdIssueDate = $emiratesIdInput['issue_date'] ?? $emiratesIdInput['issue date'] ?? null;
|
|
$emiratesIdEmployer = $emiratesIdInput['employer'] ?? null;
|
|
$emiratesIdIssuePlace = $emiratesIdInput['issue_place'] ?? $emiratesIdInput['issue place'] ?? null;
|
|
$emiratesIdOccupation = $emiratesIdInput['occupation'] ?? $emiratesIdInput['occuption'] ?? null;
|
|
}
|
|
|
|
$emiratesIdPath = null;
|
|
|
|
$licensePath = null;
|
|
$apiToken = Str::random(80);
|
|
|
|
$sponsor = DB::transaction(function () use (
|
|
$request, $email, $licensePath, $emiratesIdPath, $emiratesId, $apiToken, $licenseExpiry, $licenseNumber, $licenseInput,
|
|
$emiratesIdExpiry, $emiratesIdName, $emiratesIdDob, $emiratesIdIssueDate, $emiratesIdEmployer,
|
|
$emiratesIdIssuePlace, $emiratesIdOccupation, $licenseData
|
|
) {
|
|
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,
|
|
'emirates_id' => $emiratesId,
|
|
'license_expiry' => $licenseExpiry,
|
|
'license_number' => $licenseNumber,
|
|
'license_data' => $licenseInput,
|
|
'status' => 'active',
|
|
'is_verified' => false,
|
|
'subscription_status' => 'none',
|
|
'api_token' => $apiToken,
|
|
'fcm_token' => $request->fcm_token,
|
|
'emirates_id_name' => $emiratesIdName,
|
|
'emirates_id_dob' => $emiratesIdDob,
|
|
'emirates_id_issue_date' => $emiratesIdIssueDate,
|
|
'emirates_id_expiry' => $emiratesIdExpiry,
|
|
'emirates_id_employer' => $emiratesIdEmployer,
|
|
'emirates_id_issue_place' => $emiratesIdIssuePlace,
|
|
'emirates_id_occupation' => $emiratesIdOccupation,
|
|
'emirates_id_nationality' => $emiratesIdNationality,
|
|
'emirates_id_gender' => $emiratesIdGender,
|
|
]);
|
|
});
|
|
|
|
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' => [
|
|
'id' => $sponsor->id,
|
|
'full_name' => $sponsor->full_name,
|
|
'organization_name' => $sponsor->organization_name,
|
|
'mobile' => $sponsor->mobile,
|
|
'email' => $sponsor->email,
|
|
'nationality' => $sponsor->nationality,
|
|
'city' => $sponsor->city,
|
|
'address' => $sponsor->address,
|
|
'country_code' => $sponsor->country_code,
|
|
'is_verified' => $sponsor->is_verified,
|
|
'status' => $sponsor->status,
|
|
'license_file' => $sponsor->license_file ? asset($sponsor->license_file) : null,
|
|
'emirates_id_file' => $sponsor->emirates_id_file ? asset($sponsor->emirates_id_file) : null,
|
|
'license_expiry' => $sponsor->license_expiry ? $sponsor->license_expiry->toDateString() : null,
|
|
'license_number' => $sponsor->license_number,
|
|
'license' => $sponsor->license_data,
|
|
'validity' => $sponsor->license_expiry ? ($sponsor->license_expiry->isFuture() ? 'Valid' : 'Expired') : 'Pending Review',
|
|
'joined_at' => $sponsor->created_at->toIso8601String(),
|
|
'emirates_id' => [
|
|
'emirates_id_number' => $sponsor->emirates_id,
|
|
'name' => $sponsor->emirates_id_name,
|
|
'date_of_birth' => $sponsor->emirates_id_dob,
|
|
'issue_date' => $sponsor->emirates_id_issue_date,
|
|
'expiry_date' => $sponsor->emirates_id_expiry,
|
|
'employer' => $sponsor->emirates_id_employer,
|
|
'issue_place' => $sponsor->emirates_id_issue_place,
|
|
'occupation' => $sponsor->emirates_id_occupation,
|
|
'nationality' => $sponsor->emirates_id_nationality,
|
|
'gender' => $sponsor->emirates_id_gender,
|
|
],
|
|
],
|
|
'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' => [
|
|
'id' => $sponsor->id,
|
|
'full_name' => $sponsor->full_name,
|
|
'organization_name' => $sponsor->organization_name,
|
|
'mobile' => $sponsor->mobile,
|
|
'email' => $sponsor->email,
|
|
'nationality' => $sponsor->nationality,
|
|
'city' => $sponsor->city,
|
|
'address' => $sponsor->address,
|
|
'country_code' => $sponsor->country_code,
|
|
'is_verified' => $sponsor->is_verified,
|
|
'status' => $sponsor->status,
|
|
'license_file' => $sponsor->license_file ? asset($sponsor->license_file) : null,
|
|
'emirates_id_file' => $sponsor->emirates_id_file ? asset($sponsor->emirates_id_file) : null,
|
|
'license_expiry' => $sponsor->license_expiry ? $sponsor->license_expiry->toDateString() : null,
|
|
'license_number' => $sponsor->license_number,
|
|
'license' => $sponsor->license_data,
|
|
'validity' => $sponsor->license_expiry ? ($sponsor->license_expiry->isFuture() ? 'Valid' : 'Expired') : 'Pending Review',
|
|
'joined_at' => $sponsor->created_at->toIso8601String(),
|
|
'emirates_id' => [
|
|
'emirates_id_number' => $sponsor->emirates_id,
|
|
'name' => $sponsor->emirates_id_name,
|
|
'date_of_birth' => $sponsor->emirates_id_dob,
|
|
'issue_date' => $sponsor->emirates_id_issue_date,
|
|
'expiry_date' => $sponsor->emirates_id_expiry,
|
|
'employer' => $sponsor->emirates_id_employer,
|
|
'issue_place' => $sponsor->emirates_id_issue_place,
|
|
'occupation' => $sponsor->emirates_id_occupation,
|
|
],
|
|
],
|
|
'token' => $apiToken,
|
|
]
|
|
], 200);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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'];
|
|
$extractedNumber = $ocrLicense['license_number'];
|
|
|
|
$sponsor->update([
|
|
'license_file' => null,
|
|
'license_expiry' => $extractedExpiry,
|
|
'license_number' => $extractedNumber,
|
|
'license_data' => $ocrLicense,
|
|
]);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 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();
|
|
}
|
|
|
|
if (!$sponsor || !$sponsor->email) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'user not found this email id',
|
|
], 404);
|
|
}
|
|
|
|
$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.',
|
|
]);
|
|
}
|
|
}
|
|
|