sms integration
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\ItemCollection;
|
||||
use App\Jobs\SendSmsJob;
|
||||
use App\Models\Area;
|
||||
use App\Models\BlockUser;
|
||||
use App\Models\Blog;
|
||||
use App\Models\Category;
|
||||
use App\Models\Chat;
|
||||
use App\Models\City;
|
||||
use App\Models\Client;
|
||||
use App\Models\Clientsreg;
|
||||
use App\Models\ContactUs;
|
||||
use App\Models\Country;
|
||||
use App\Models\Currency;
|
||||
@@ -173,10 +176,10 @@ class ApiController extends Controller
|
||||
|
||||
// First try strict match
|
||||
$user = User::where('type', 'phone')
|
||||
->where('mobile', $mobile)
|
||||
->where('mobile', $mobile)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
|
||||
if (!$user) {
|
||||
return ResponseService::errorResponse(
|
||||
__('User not found. Please signup first.')
|
||||
@@ -3950,145 +3953,10 @@ class ApiController extends Controller
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
public function getOtp(Request $request)
|
||||
{
|
||||
try {
|
||||
$validator = Validator::make($request->query(), [
|
||||
'country_code' => 'required|string|max:5',
|
||||
'number' => 'required|string|max:15',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return ResponseService::validationError($validator->errors()->first());
|
||||
}
|
||||
|
||||
// Normalize inputs
|
||||
$countryCode = ltrim(trim($request->query('country_code')), '+');
|
||||
$number = preg_replace('/\D/', '', $request->query('number'));
|
||||
|
||||
$toNumber = '+' . $countryCode . $number;
|
||||
|
||||
$twilioSettings = Setting::whereIn('name', [
|
||||
'twilio_account_sid',
|
||||
'twilio_auth_token',
|
||||
'twilio_my_phone_number',
|
||||
])->pluck('value', 'name');
|
||||
|
||||
if ($twilioSettings->count() < 3) {
|
||||
return ResponseService::errorResponse(__('Twilio settings are missing.'));
|
||||
}
|
||||
|
||||
$client = new TwilioRestClient(
|
||||
$twilioSettings['twilio_account_sid'],
|
||||
$twilioSettings['twilio_auth_token']
|
||||
);
|
||||
|
||||
try {
|
||||
$client->lookups->v1->phoneNumbers($toNumber)->fetch();
|
||||
} catch (\Throwable $e) {
|
||||
return ResponseService::errorResponse(__('Invalid phone number.'));
|
||||
}
|
||||
|
||||
$otp = rand(100000, 999999);
|
||||
$expireAt = now()->addMinutes(10);
|
||||
|
||||
NumberOtp::updateOrCreate(
|
||||
[
|
||||
'number' => $number,
|
||||
],
|
||||
[
|
||||
'otp' => $otp,
|
||||
'expire_at' => $expireAt,
|
||||
]
|
||||
);
|
||||
|
||||
// Send OTP
|
||||
$client->messages->create($toNumber, [
|
||||
'from' => $twilioSettings['twilio_my_phone_number'],
|
||||
'body' => "Your OTP is: $otp. It expires in 10 minutes.",
|
||||
]);
|
||||
|
||||
return ResponseService::successResponse(__('OTP sent successfully.'));
|
||||
} catch (\Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'OTP Controller -> getOtp');
|
||||
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
public function verifyOtp(Request $request)
|
||||
{
|
||||
try {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'number' => 'required|string',
|
||||
'country_code' => 'required|string',
|
||||
'otp' => 'required|numeric|digits:6',
|
||||
'password' => 'nullable|string|min:6',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return ResponseService::validationError($validator->errors()->first());
|
||||
}
|
||||
|
||||
$number = $request->query('number');
|
||||
$countryCode = $request->query('country_code');
|
||||
|
||||
// Format full phone number
|
||||
// $requestNumber = $countryCode.$number;
|
||||
|
||||
// $trimmedNumber = ltrim($requestNumber, '+');
|
||||
// $toNumber = '+'.$trimmedNumber;
|
||||
|
||||
$otpRecord = NumberOtp::where('number', $number)->first();
|
||||
if (! $otpRecord) {
|
||||
return ResponseService::errorResponse(__('OTP not found.'));
|
||||
}
|
||||
if (now()->isAfter($otpRecord->expire_at)) {
|
||||
return ResponseService::validationError(__('OTP has expired.'));
|
||||
}
|
||||
|
||||
if ($otpRecord->attempts >= 3) {
|
||||
$otpRecord->delete();
|
||||
|
||||
return ResponseService::validationError(__('OTP expired after 3 failed attempts.'));
|
||||
}
|
||||
|
||||
if ($otpRecord->otp != $request->otp) {
|
||||
$otpRecord->increment('attempts');
|
||||
|
||||
return ResponseService::validationError(__('Invalid OTP.'));
|
||||
}
|
||||
$otpRecord->delete();
|
||||
|
||||
$user = User::where('mobile', $request->number)->where('type', 'phone')->first();
|
||||
|
||||
if (! $user) {
|
||||
$user = User::create([
|
||||
'mobile' => $request->number,
|
||||
'type' => 'phone',
|
||||
'country_code' => $countryCode,
|
||||
'password' => ! empty($request->password) ? Hash::make($request->password) : '',
|
||||
]);
|
||||
$user->assignRole('User');
|
||||
}else{
|
||||
if (! empty($countryCode)) {
|
||||
$user->country_code = ltrim($countryCode, '+');
|
||||
}
|
||||
$user->save();
|
||||
}
|
||||
|
||||
Auth::login($user);
|
||||
$auth = User::find(Auth::id());
|
||||
|
||||
$token = $auth->createToken($auth->name ?? '')->plainTextToken;
|
||||
|
||||
return ResponseService::successResponse(__('User logged-in successfully'), $auth, ['token' => $token]);
|
||||
} catch (Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'OTP Controller -> verifyOtp');
|
||||
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
public function userExists(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
@@ -4109,7 +3977,7 @@ class ApiController extends Controller
|
||||
->where('mobile', $mobile)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
|
||||
if (! $userExists) {
|
||||
return ResponseService::errorResponse(
|
||||
__('User does not exist'),
|
||||
|
||||
320
app/Http/Controllers/api/AuthController.php
Normal file
320
app/Http/Controllers/api/AuthController.php
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers\api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\SendSmsJob;
|
||||
use App\Models\Clientsreg;
|
||||
use App\Services\ResponseService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/get-otp",
|
||||
* summary="Send OTP for registration",
|
||||
* tags={"Auth"},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"phone","device_type","password"},
|
||||
* @OA\Property(property="firstname", type="string", example="Devit"),
|
||||
* @OA\Property(property="lastname", type="string", example="Togo"),
|
||||
* @OA\Property(property="phone", type="string", example="901234567"),
|
||||
* @OA\Property(property="password", type="string", example="1234"),
|
||||
* @OA\Property(property="device_type", type="string", example="android"),
|
||||
* @OA\Property(property="fcm_token", type="string", example="token_here")
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="OTP sent successfully"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
public function getOtp(Request $request)
|
||||
{
|
||||
try {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'firstname' => 'nullable|string|max:100',
|
||||
'lastname' => 'nullable|string|max:100',
|
||||
'phone' => 'required|string|max:9',
|
||||
'fcm_token' => 'nullable|string',
|
||||
'device_type' => 'required|in:android,ios,web',
|
||||
'password' => 'required|string|min:4',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return ResponseService::validationError($validator->errors()->first());
|
||||
}
|
||||
|
||||
$phone = preg_replace('/\D/', '', $request->phone);
|
||||
|
||||
$client = Clientsreg::where('phone', $phone)->first();
|
||||
|
||||
if ($client) {
|
||||
|
||||
if ($client->active) {
|
||||
return ResponseService::errorResponse(__('You are already registered. Log in.'));
|
||||
}
|
||||
|
||||
if ($client->code_expires_at && now()->lt($client->code_expires_at)) {
|
||||
return ResponseService::errorResponse(__('The previous code is still valid. Please wait.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$otp = rand(1000, 9999);
|
||||
|
||||
$client = Clientsreg::updateOrCreate(
|
||||
['phone' => $phone],
|
||||
[
|
||||
'firstname' => $request->firstname,
|
||||
'lastname' => $request->lastname,
|
||||
'fcm_token' => $request->fcm_token,
|
||||
'device_type' => $request->device_type,
|
||||
'password' => bcrypt($request->password),
|
||||
'otp' => $otp, // ✅ MUHIM
|
||||
'code_sent_at' => now(),
|
||||
'code_expires_at' => now()->addMinutes(1),
|
||||
'active' => false,
|
||||
]
|
||||
);
|
||||
|
||||
dispatch(new SendSmsJob($phone, $otp));
|
||||
|
||||
return ResponseService::successResponse(__('OTP sent successfully.'));
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'OTP Controller -> getOtp');
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/verify-otp",
|
||||
* summary="Verify OTP code",
|
||||
* tags={"Auth"},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"phone","otp"},
|
||||
* @OA\Property(property="phone", type="string", example="998901234567"),
|
||||
* @OA\Property(property="otp", type="integer", example=1234)
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Phone verified successfully"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function verifyOtp(Request $request)
|
||||
{
|
||||
try {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'phone' => 'required|string',
|
||||
'otp' => 'required|numeric|digits:4',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return ResponseService::validationError($validator->errors()->first());
|
||||
}
|
||||
|
||||
$phone = preg_replace('/\D/', '', $request->phone);
|
||||
|
||||
|
||||
$client = Clientsreg::where('phone', $phone)->first();
|
||||
|
||||
if (!$client) {
|
||||
return ResponseService::errorResponse(__('User not found.'));
|
||||
}
|
||||
|
||||
|
||||
if (!$client->code_expires_at || now()->gt($client->code_expires_at)) {
|
||||
return ResponseService::validationError(__('OTP has expired.'));
|
||||
}
|
||||
|
||||
|
||||
if ($client->otp != $request->otp) {
|
||||
return ResponseService::validationError(__('Invalid OTP.'));
|
||||
}
|
||||
|
||||
|
||||
$client->active = true;
|
||||
$client->otp = null;
|
||||
$client->code_expires_at = null;
|
||||
$client->save();
|
||||
|
||||
return ResponseService::successResponse(__('Phone verified successfully'), [
|
||||
'phone' => $phone,
|
||||
'verified' => true,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'OTP Controller -> verifyOtp');
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/login",
|
||||
* summary="User login",
|
||||
* tags={"Auth"},
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"phone","password","device_type"},
|
||||
* @OA\Property(property="phone", type="string", example="998901234567"),
|
||||
* @OA\Property(property="password", type="string", example="1234"),
|
||||
* @OA\Property(property="device_type", type="string", example="android"),
|
||||
* @OA\Property(property="fcm_token", type="string", example="token_here")
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Login successful",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="token", type="string", example="1|abcdefg123456")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$validator = Validator::make($request->all(), [
|
||||
'phone' => 'required|string|max:15',
|
||||
'password' => 'required|string|min:4',
|
||||
'device_type' => 'required|in:android,ios,web',
|
||||
'fcm_token' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return ResponseService::validationError($validator->errors()->first());
|
||||
}
|
||||
|
||||
|
||||
$phone = preg_replace('/\D/', '', $request->phone);
|
||||
|
||||
|
||||
$client = Clientsreg::where('phone', $phone)->first();
|
||||
|
||||
if (!$client) {
|
||||
return ResponseService::errorResponse(__('User not found.'));
|
||||
}
|
||||
|
||||
if (!$client->active) {
|
||||
return ResponseService::errorResponse(__('Account not verified.'));
|
||||
}
|
||||
|
||||
|
||||
if (!Hash::check($request->password, $client->password)) {
|
||||
return ResponseService::errorResponse(__('Invalid password.'));
|
||||
}
|
||||
|
||||
|
||||
$client->update([
|
||||
'device_type' => $request->device_type,
|
||||
'fcm_token' => $request->fcm_token,
|
||||
]);
|
||||
|
||||
|
||||
$token = $client->createToken('client-token')->plainTextToken;
|
||||
|
||||
return ResponseService::successResponse(__('Login successful'), [
|
||||
'token' => $token,
|
||||
// 'user' => $client,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'Auth -> login');
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/profile",
|
||||
* summary="Get user profile",
|
||||
* tags={"Auth"},
|
||||
* security={{"bearerAuth":{}}},
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Profile data",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="firstname", type="string", example="Ali"),
|
||||
* @OA\Property(property="lastname", type="string", example="Valiyev"),
|
||||
* @OA\Property(property="phone", type="string", example="998901234567"),
|
||||
* @OA\Property(property="email", type="string", example="test@gmail.com"),
|
||||
* @OA\Property(property="device_type", type="string", example="android")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function profile(Request $request)
|
||||
{
|
||||
$client = $request->user();
|
||||
|
||||
return ResponseService::successResponse(__('Profile data'), [
|
||||
'id' => $client->id,
|
||||
'firstname' => $client->firstname,
|
||||
'lastname' => $client->lastname,
|
||||
'phone' => $client->phone,
|
||||
'email' => $client->email,
|
||||
'device_type' => $client->device_type,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/logout",
|
||||
* summary="Logout user",
|
||||
* tags={"Auth"},
|
||||
* security={{"bearerAuth":{}}},
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Logged out successfully"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
|
||||
return ResponseService::successResponse(__('Logged out successfully'));
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
ResponseService::logErrorResponse($th, 'Auth -> logout');
|
||||
return ResponseService::errorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,8 +41,7 @@ class SendSmsJob implements ShouldQueue
|
||||
|
||||
$postData = [
|
||||
'mobile_phone' => $phoneNumber,
|
||||
'message' => "DELGO hisobiga kirish uchun kod: {$this->code} Ushbu kodni ulashmang! #WBcz5ARFKcp",
|
||||
// 'message' => "DELGO hisobiga kirish uchun kod: {$this->code} Ushbu kodni ulashmang! #WBcz5ARFKcp",
|
||||
'message' => "pedagog.uz sayti va mobil ilovasi uchun tasdiqlash kodi: {$this->code}",
|
||||
'from' => "4546"
|
||||
];
|
||||
|
||||
|
||||
38
app/Models/Clientsreg.php
Normal file
38
app/Models/Clientsreg.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class Clientsreg extends Model
|
||||
{
|
||||
use HasApiTokens, HasFactory;
|
||||
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'firstname',
|
||||
'lastname',
|
||||
'phone',
|
||||
'otp',
|
||||
'email',
|
||||
'fcm_token',
|
||||
'device_type',
|
||||
'active',
|
||||
'code_sent_at',
|
||||
'code_expires_at',
|
||||
'password',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'active' => 'boolean',
|
||||
'code_sent_at' => 'datetime',
|
||||
'code_expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password',
|
||||
];
|
||||
}
|
||||
@@ -27,6 +27,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
Schema::defaultStringLength(191);
|
||||
URL::forceScheme('https');
|
||||
URL::forceScheme('http');
|
||||
}
|
||||
}
|
||||
|
||||
36
app/Providers/HorizonServiceProvider.php
Normal file
36
app/Providers/HorizonServiceProvider.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Horizon\Horizon;
|
||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||
|
||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Horizon::routeSmsNotificationsTo('15556667777');
|
||||
// Horizon::routeMailNotificationsTo('example@example.com');
|
||||
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Horizon gate.
|
||||
*
|
||||
* This gate determines who can access Horizon in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewHorizon', function ($user = null) {
|
||||
return in_array(optional($user)->email, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class ResponseService
|
||||
public static function successResponse(?string $message = 'Success', $data = null, array $customData = [], $code = null): void
|
||||
{
|
||||
response()->json(array_merge([
|
||||
'error' => false,
|
||||
// 'error' => false,
|
||||
'message' => trans($message),
|
||||
'data' => $data,
|
||||
'code' => $code ?? config('constants.RESPONSE_CODE.SUCCESS'),
|
||||
|
||||
Reference in New Issue
Block a user