sms integration

This commit is contained in:
Devit
2026-03-29 14:27:45 +05:00
parent c02f5d5bf8
commit 08c3acfc60
16 changed files with 15804 additions and 14306 deletions

View File

@@ -3,12 +3,15 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Resources\ItemCollection; use App\Http\Resources\ItemCollection;
use App\Jobs\SendSmsJob;
use App\Models\Area; use App\Models\Area;
use App\Models\BlockUser; use App\Models\BlockUser;
use App\Models\Blog; use App\Models\Blog;
use App\Models\Category; use App\Models\Category;
use App\Models\Chat; use App\Models\Chat;
use App\Models\City; use App\Models\City;
use App\Models\Client;
use App\Models\Clientsreg;
use App\Models\ContactUs; use App\Models\ContactUs;
use App\Models\Country; use App\Models\Country;
use App\Models\Currency; use App\Models\Currency;
@@ -173,10 +176,10 @@ class ApiController extends Controller
// First try strict match // First try strict match
$user = User::where('type', 'phone') $user = User::where('type', 'phone')
->where('mobile', $mobile) ->where('mobile', $mobile)
->withTrashed() ->withTrashed()
->first(); ->first();
if (!$user) { if (!$user) {
return ResponseService::errorResponse( return ResponseService::errorResponse(
__('User not found. Please signup first.') __('User not found. Please signup first.')
@@ -3950,145 +3953,10 @@ class ApiController extends Controller
return ResponseService::errorResponse(); 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) public function userExists(Request $request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
@@ -4109,7 +3977,7 @@ class ApiController extends Controller
->where('mobile', $mobile) ->where('mobile', $mobile)
->withTrashed() ->withTrashed()
->first(); ->first();
if (! $userExists) { if (! $userExists) {
return ResponseService::errorResponse( return ResponseService::errorResponse(
__('User does not exist'), __('User does not exist'),

View 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();
}
}
}

View File

@@ -41,8 +41,7 @@ class SendSmsJob implements ShouldQueue
$postData = [ $postData = [
'mobile_phone' => $phoneNumber, 'mobile_phone' => $phoneNumber,
'message' => "DELGO hisobiga kirish uchun kod: {$this->code} Ushbu kodni ulashmang! #WBcz5ARFKcp", 'message' => "pedagog.uz sayti va mobil ilovasi uchun tasdiqlash kodi: {$this->code}",
// 'message' => "DELGO hisobiga kirish uchun kod: {$this->code} Ushbu kodni ulashmang! #WBcz5ARFKcp",
'from' => "4546" 'from' => "4546"
]; ];

38
app/Models/Clientsreg.php Normal file
View 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',
];
}

View File

@@ -27,6 +27,6 @@ class AppServiceProvider extends ServiceProvider
public function boot() public function boot()
{ {
Schema::defaultStringLength(191); Schema::defaultStringLength(191);
URL::forceScheme('https'); URL::forceScheme('http');
} }
} }

View 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, [
//
]);
});
}
}

View File

@@ -137,7 +137,7 @@ class ResponseService
public static function successResponse(?string $message = 'Success', $data = null, array $customData = [], $code = null): void public static function successResponse(?string $message = 'Success', $data = null, array $customData = [], $code = null): void
{ {
response()->json(array_merge([ response()->json(array_merge([
'error' => false, // 'error' => false,
'message' => trans($message), 'message' => trans($message),
'data' => $data, 'data' => $data,
'code' => $code ?? config('constants.RESPONSE_CODE.SUCCESS'), 'code' => $code ?? config('constants.RESPONSE_CODE.SUCCESS'),

View File

@@ -14,6 +14,7 @@
"ext-zip": "*", "ext-zip": "*",
"cerbero/json-parser": "^1.1", "cerbero/json-parser": "^1.1",
"dacoto/laravel-wizard-installer": "^1.0", "dacoto/laravel-wizard-installer": "^1.0",
"darkaonline/l5-swagger": "^8.6",
"devaslanphp/auto-translate": "*", "devaslanphp/auto-translate": "*",
"doctrine/dbal": "^3.6", "doctrine/dbal": "^3.6",
"flutterwavedev/flutterwave-v3": "^1.0", "flutterwavedev/flutterwave-v3": "^1.0",
@@ -23,6 +24,7 @@
"kingflamez/laravelrave": "4.2.1", "kingflamez/laravelrave": "4.2.1",
"kornrunner/blurhash": "^1.2", "kornrunner/blurhash": "^1.2",
"laravel/framework": "^10.0", "laravel/framework": "^10.0",
"laravel/horizon": "^5.45",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.2",
"laravel/tinker": "^2.7", "laravel/tinker": "^2.7",
"laravel/ui": "^4.0", "laravel/ui": "^4.0",

28681
composer.lock generated

File diff suppressed because it is too large Load Diff

254
config/horizon.php Normal file
View File

@@ -0,0 +1,254 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Horizon Name
|--------------------------------------------------------------------------
|
| This name appears in notifications and in the Horizon UI. Unique names
| can be useful while running multiple instances of Horizon within an
| application, allowing you to identify the Horizon you're viewing.
|
*/
'name' => env('HORIZON_NAME'),
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Horizon will be accessible from. If this
| setting is null, Horizon will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
'domain' => env('HORIZON_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('HORIZON_PATH', 'horizon'),
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| meta information required for it to function. It includes the list
| of supervisors, failed jobs, job metrics, and other information.
|
*/
'use' => 'default',
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix when you are running multiple installations
| of Horizon on the same server so that they don't have problems.
|
*/
'prefix' => env(
'HORIZON_PREFIX',
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
),
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => ['web'],
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the LongWaitDetected event
| will be fired. Every connection / queue combination may have its
| own, unique threshold (in seconds) before this event is fired.
|
*/
'waits' => [
'redis:default' => 60,
],
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| Here you can configure for how long (in minutes) you desire Horizon to
| persist the recent and failed jobs. Typically, recent jobs are kept
| for one hour while all failed jobs are stored for an entire week.
|
*/
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Silenced Jobs
|--------------------------------------------------------------------------
|
| Silencing a job will instruct Horizon to not place the job in the list
| of completed jobs within the Horizon dashboard. This setting may be
| used to fully remove any noisy jobs from the completed jobs list.
|
*/
'silenced' => [
// App\Jobs\ExampleJob::class,
],
'silenced_tags' => [
// 'notifications',
],
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait on all of the workers to terminate unless the --wait option
| is provided. Fast termination can shorten deployment delay by
| allowing a new instance of Horizon to start while the last
| instance will continue to terminate each of its workers.
|
*/
'fast_termination' => false,
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon master
| supervisor may consume before it is terminated and restarted. For
| configuring these limits on your workers, see the next section.
|
*/
'memory_limit' => 64,
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
*/
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'maxProcesses' => 1,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 60,
'nice' => 0,
],
],
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
/*
|--------------------------------------------------------------------------
| File Watcher Configuration
|--------------------------------------------------------------------------
|
| The following list of directories and files will be watched when using
| the `horizon:listen` command. Whenever any directories or files are
| changed, Horizon will automatically restart to apply all changes.
|
*/
'watch' => [
'app',
'bootstrap',
'config/**/*.php',
'database/**/*.php',
'public/**/*.php',
'resources/**/*.php',
'routes',
'composer.lock',
'composer.json',
'.env',
],
];

318
config/l5-swagger.php Normal file
View File

@@ -0,0 +1,318 @@
<?php
return [
'default' => 'default',
'documentations' => [
'default' => [
'api' => [
'title' => 'L5 Swagger UI',
],
'routes' => [
/*
* Route for accessing api documentation interface
*/
'api' => 'api/documentation',
],
'paths' => [
/*
* Edit to include full URL in ui for assets
*/
'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true),
/*
* Edit to set path where swagger ui assets should be stored
*/
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
/*
* File name of the generated json documentation file
*/
'docs_json' => 'api-docs.json',
/*
* File name of the generated YAML documentation file
*/
'docs_yaml' => 'api-docs.yaml',
/*
* Set this to `json` or `yaml` to determine which documentation file to use in UI
*/
'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'),
/*
* Absolute paths to directory containing the swagger annotations are stored.
*/
'annotations' => [
base_path('app'),
],
],
],
],
'defaults' => [
'routes' => [
/*
* Route for accessing parsed swagger annotations.
*/
'docs' => 'docs',
/*
* Route for Oauth2 authentication callback.
*/
'oauth2_callback' => 'api/oauth2-callback',
/*
* Middleware allows to prevent unexpected access to API documentation
*/
'middleware' => [
'api' => [],
'asset' => [],
'docs' => [],
'oauth2_callback' => [],
],
/*
* Route Group options
*/
'group_options' => [],
],
'paths' => [
/*
* Absolute path to location where parsed annotations will be stored
*/
'docs' => storage_path('api-docs'),
/*
* Absolute path to directory where to export views
*/
'views' => base_path('resources/views/vendor/l5-swagger'),
/*
* Edit to set the api's base path
*/
'base' => env('L5_SWAGGER_BASE_PATH', null),
/*
* Absolute path to directories that should be excluded from scanning
* @deprecated Please use `scanOptions.exclude`
* `scanOptions.exclude` overwrites this
*/
'excludes' => [],
],
'scanOptions' => [
/**
* Configuration for default processors. Allows to pass processors configuration to swagger-php.
*
* @link https://zircote.github.io/swagger-php/reference/processors.html
*/
'default_processors_configuration' => [
/** Example */
/**
* 'operationId.hash' => true,
* 'pathFilter' => [
* 'tags' => [
* '/pets/',
* '/store/',
* ],
* ],.
*/
],
/**
* analyser: defaults to \OpenApi\StaticAnalyser .
*
* @see \OpenApi\scan
*/
'analyser' => null,
/**
* analysis: defaults to a new \OpenApi\Analysis .
*
* @see \OpenApi\scan
*/
'analysis' => null,
/**
* Custom query path processors classes.
*
* @link https://github.com/zircote/swagger-php/tree/master/Examples/processors/schema-query-parameter
* @see \OpenApi\scan
*/
'processors' => [
// new \App\SwaggerProcessors\SchemaQueryParameter(),
],
/**
* pattern: string $pattern File pattern(s) to scan (default: *.php) .
*
* @see \OpenApi\scan
*/
'pattern' => null,
/*
* Absolute path to directories that should be excluded from scanning
* @note This option overwrites `paths.excludes`
* @see \OpenApi\scan
*/
'exclude' => [],
/*
* Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0.
* By default the spec will be in version 3.0.0
*/
'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION),
],
/*
* API security definitions. Will be generated into documentation file.
*/
'securityDefinitions' => [
'securitySchemes' => [
/*
* Examples of Security schemes
*/
/*
'api_key_security_example' => [ // Unique name of security
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for security scheme',
'name' => 'api_key', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
'oauth2_security_example' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for oauth2 security scheme.',
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
'scopes' => [
'read:projects' => 'read your projects',
'write:projects' => 'modify projects in your account',
]
],
*/
/* Open API 3.0 support
'passport' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'Laravel passport oauth2 security.',
'in' => 'header',
'scheme' => 'https',
'flows' => [
"password" => [
"authorizationUrl" => config('app.url') . '/oauth/authorize',
"tokenUrl" => config('app.url') . '/oauth/token',
"refreshUrl" => config('app.url') . '/token/refresh',
"scopes" => []
],
],
],
'sanctum' => [ // Unique name of security
'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
'description' => 'Enter token in format (Bearer <token>)',
'name' => 'Authorization', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
*/
],
'security' => [
/*
* Examples of Securities
*/
[
/*
'oauth2_security_example' => [
'read',
'write'
],
'passport' => []
*/
],
],
],
/*
* Set this to `true` in development mode so that docs would be regenerated on each request
* Set this to `false` to disable swagger generation on production
*/
'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),
/*
* Set this to `true` to generate a copy of documentation in yaml format
*/
'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false),
/*
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
* string[]
*/
'proxy' => false,
/*
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
* See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
*/
'additional_config_url' => null,
/*
* Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
* 'method' (sort by HTTP method).
* Default is the order returned by the server unchanged.
*/
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
/*
* Pass the validatorUrl parameter to SwaggerUi init on the JS side.
* A null value here disables validation.
*/
'validator_url' => null,
/*
* Swagger UI configuration parameters
*/
'ui' => [
'display' => [
'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false),
/*
* Controls the default expansion setting for the operations and tags. It can be :
* 'list' (expands only the tags),
* 'full' (expands the tags and operations),
* 'none' (expands nothing).
*/
'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'),
/**
* If set, enables filtering. The top bar will show an edit box that
* you can use to filter the tagged operations that are shown. Can be
* Boolean to enable or disable, or a string, in which case filtering
* will be enabled using that string as the filter expression. Filtering
* is case-sensitive matching the filter expression anywhere inside
* the tag.
*/
'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false
],
'authorization' => [
/*
* If set to true, it persists authorization data, and it would not be lost on browser close/refresh
*/
'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false),
'oauth2' => [
/*
* If set to true, adds PKCE to AuthorizationCodeGrant flow
*/
'use_pkce_with_authorization_code_grant' => false,
],
],
],
/*
* Constants which can be used in annotations
*/
'constants' => [
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
],
],
];

View File

@@ -31,4 +31,10 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
], ],
'eskiz' => [
'email' => env('ESKIZ_EMAIL', ''),
'password' => env('ESKIZ_PASSWORD', ''),
],
]; ];

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('clientsregs', function (Blueprint $table) {
$table->id();
$table->string('firstname',100)->nullable();
$table->string('lastname',100)->nullable();
$table->string('phone',15)->unique();
$table->string('otp',15)->nullable();
$table->string('email', 150)->unique()->nullable()->comment('Foydalanuvchi email manzili');
$table->text('fcm_token')->nullable()->comment('Firebase token manzili');
$table->enum('device_type', ['android', 'ios', 'web'])->nullable()->comment('Qurilma turi');
$table->boolean('active')->default(false);
$table->timestamp('code_sent_at')->nullable();
$table->timestamp('code_expires_at')->nullable();
$table->string('password',)->nullable()->comment('Parol');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('clientsregs');
}
};

View File

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Http\Controllers\api\AuthController;
use App\Http\Controllers\ApiController; use App\Http\Controllers\ApiController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -72,11 +73,23 @@ Route::group(['middleware' => ['auth:sanctum']], static function () {
}); });
Route::post('get-otp', [AuthController::class, 'getOtp']);
Route::post('verify-otp', [AuthController::class, 'verifyOtp']);
Route::post('login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('profile', [AuthController::class, 'profile']);
Route::get('logout', [AuthController::class, 'logout']);
});
/* Non Authenticated Routes */ /* Non Authenticated Routes */
Route::get('user-exists', [ApiController::class, 'userExists']); Route::get('user-exists', [ApiController::class, 'userExists']);
Route::get('get-currencies', [ApiController::class, 'getCurrencies']); Route::get('get-currencies', [ApiController::class, 'getCurrencies']);
Route::get('get-otp', [ApiController::class, 'getOtp']);
Route::get('verify-otp', [ApiController::class, 'verifyOtp']);
Route::get('get-package', [ApiController::class, 'getPackage']); Route::get('get-package', [ApiController::class, 'getPackage']);
Route::get('get-languages', [ApiController::class, 'getLanguages']); Route::get('get-languages', [ApiController::class, 'getLanguages']);
Route::post('user-signup', [ApiController::class, 'userSignup']); Route::post('user-signup', [ApiController::class, 'userSignup']);

View File

@@ -0,0 +1,249 @@
{
"openapi": "3.0.0",
"info": {
"title": "Eclassify API",
"description": "Eclassify api documentation",
"version": "1.0.0"
},
"paths": {
"/api/get-otp": {
"post": {
"tags": [
"Auth"
],
"summary": "Send OTP for registration",
"operationId": "9665576026de3c8975fefbf59380584a",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"phone",
"device_type",
"password"
],
"properties": {
"firstname": {
"type": "string",
"example": "Devit"
},
"lastname": {
"type": "string",
"example": "Togo"
},
"phone": {
"type": "string",
"example": "901234567"
},
"password": {
"type": "string",
"example": "1234"
},
"device_type": {
"type": "string",
"example": "android"
},
"fcm_token": {
"type": "string",
"example": "token_here"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "OTP sent successfully"
}
}
}
},
"/api/verify-otp": {
"post": {
"tags": [
"Auth"
],
"summary": "Verify OTP code",
"operationId": "1f09b8b2173f8afbe842b19937649fbb",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"phone",
"otp"
],
"properties": {
"phone": {
"type": "string",
"example": "998901234567"
},
"otp": {
"type": "integer",
"example": 1234
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Phone verified successfully"
}
}
}
},
"/api/login": {
"post": {
"tags": [
"Auth"
],
"summary": "User login",
"operationId": "dcf90ce65219b0ad51fc32e9f7f69843",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"phone",
"password",
"device_type"
],
"properties": {
"phone": {
"type": "string",
"example": "998901234567"
},
"password": {
"type": "string",
"example": "1234"
},
"device_type": {
"type": "string",
"example": "android"
},
"fcm_token": {
"type": "string",
"example": "token_here"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Login successful",
"content": {
"application/json": {
"schema": {
"properties": {
"token": {
"type": "string",
"example": "1|abcdefg123456"
}
},
"type": "object"
}
}
}
}
}
}
},
"/api/profile": {
"get": {
"tags": [
"Auth"
],
"summary": "Get user profile",
"operationId": "4ed7ca3229d16b0b14785695794359e6",
"responses": {
"200": {
"description": "Profile data",
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"type": "integer",
"example": 1
},
"firstname": {
"type": "string",
"example": "Ali"
},
"lastname": {
"type": "string",
"example": "Valiyev"
},
"phone": {
"type": "string",
"example": "998901234567"
},
"email": {
"type": "string",
"example": "test@gmail.com"
},
"device_type": {
"type": "string",
"example": "android"
}
},
"type": "object"
}
}
}
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/api/logout": {
"get": {
"tags": [
"Auth"
],
"summary": "Logout user",
"operationId": "edf2b3b2f5fefa33e2fd224d2f2505c4",
"responses": {
"200": {
"description": "Logged out successfully"
}
},
"security": [
{
"bearerAuth": []
}
]
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"bearerFormat": "JWT",
"scheme": "bearer"
}
}
},
"tags": [
{
"name": "Auth",
"description": "Auth"
}
]
}