diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 51159dc..495a0dd 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -453,7 +453,59 @@ public function checkOTP(Request $request) 'token' => $token, ]); } +public function requestMobileVerification(Request $request) +{ + $data = $request->validate([ + 'mobile' => ['required', 'string', new MobileNumber], + ]); + $mobile = MobileNumberHelper::formatMobile($data['mobile']); + + if (User::where('mobile', $mobile)->where('id', '!=', auth()->id())->exists()) { + return response()->json(['message' => 'mobile already taken'], 400); + } + + $otp = rand(10000, 99999); + + auth()->user()->otpTokens()->create([ + 'token' => $otp + ]); + + SendOTPJob::dispatch($mobile, (string)$otp); + + return response()->json(['message' => 'otp sent']); +} + +public function confirmMobileVerification(Request $request) +{ + $data = $request->validate([ + 'mobile' => ['required', 'string', new MobileNumber], + 'token' => 'required|string' + ]); + + $mobile = MobileNumberHelper::formatMobile($data['mobile']); + $user = auth()->user(); + + $otp = $user->otpTokens() + ->where('token', $data['token']) + ->where('created_at', '>', now()->subMinutes(15)) + ->first(); + + if (!$otp) { + return response()->json(['message' => 'invalid token'], 400); + } + + $otp->delete(); + + $user->update([ + 'mobile' => $mobile, + 'mobile_verified_at' => now(), + ]); + + return response()->json([ + 'message' => 'mobile verified successfully' + ]); +} public function updateProfile(Request $request) { $data = $request->validate([ diff --git a/app/Models/User.php b/app/Models/User.php index bf20921..ed3c9cb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -20,6 +20,9 @@ class User extends Authenticatable 'female' => 2, ]; protected $guarded = []; + protected $casts = [ + 'mobile_verified_at' => 'datetime', +]; protected $hidden = [ 'password', ]; @@ -39,7 +42,10 @@ public function getFullNameAttribute() { return $this->first_name . ' ' . $this->last_name; } - + public function hasVerifiedMobile(): bool + { + return !is_null($this->mobile_verified_at); + } public function getAvatarAttribute() { return isset($this->attributes['avatar']) ? url($this->attributes['avatar']) : null; diff --git a/database/migrations/2026_02_24_234225_add_mobile_verified_at_to_users_table.php b/database/migrations/2026_02_24_234225_add_mobile_verified_at_to_users_table.php new file mode 100644 index 0000000..2634172 --- /dev/null +++ b/database/migrations/2026_02_24_234225_add_mobile_verified_at_to_users_table.php @@ -0,0 +1,28 @@ +timestamp('mobile_verified_at')->nullable()->after('mobile'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('mobile_verified_at'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 7758e3b..417ca7e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -55,6 +55,8 @@ Route::apiResource('reminders', ReminderController::class); }); Route::middleware('auth:sanctum')->group(function () { + Route::post('/verify-mobile/request', [UserController::class, 'requestMobileVerification']); + Route::post('/verify-mobile/confirm', [UserController::class, 'confirmMobileVerification']); Route::post('profile', [UserController::class, 'updateProfile']);