complete Complete User Request Submission section RAD-1

This commit is contained in:
2026-01-03 19:14:30 +03:30
commit 7137ec5792
393 changed files with 152602 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Services\Auth;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class AuthService
{
public function getLevelsForPhone(string $phone): array
{
session()->flush();
$users = User::where('phone', $phone)->get(['level']);
return $users
->map(fn($user) => match ($user->level) {
'پزشک' => 'doctor',
'سرپرست' => 'supervisor',
'دانپزشک' => 'veterinarian',
default => null,
})
->filter()
->unique()
->values()
->all();
}
public function authenticateByPhoneAndLevel(string $phone, string $level, string $password): ?User
{
$user = User::where('phone', $phone)
->where('level', $level)
->first();
if (! $user) return null;
if (! Hash::check($password, $user->password)) return null;
Auth::login($user);
session()->regenerate();
session()->forget('register_phone' , 'verified_phone' , 'otp_verified' , 'login_phone' , 'login_level' , 'available_levels');
return $user;
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace App\Services\Auth;
use App\Models\OtpCode;
use Illuminate\Support\Facades\Http;
class OtpService
{
public function generateCode(): string
{
return (string) random_int(1000, 9999);
}
public function sendForPhone(string $phone, int $minutes = 3): OtpCode
{
$code = $this->generateCode();
$expiresAt = now()->addMinutes($minutes);
$otpCode = OtpCode::updateOrCreate(
['phone' => $phone],
['code' => $code, 'expires_at' => $expiresAt]
);
$this->sendSms($phone, $code);
return $otpCode;
}
public function verifyCode(string $phone, string $code): bool
{
$deleted = OtpCode::where('phone', $phone)
->where('code', $code)
->where('expires_at', '>', now())
->delete();
return (bool) $deleted;
}
public function existsValid(string $phone, string $code): bool
{
return OtpCode::where('phone', $phone)
->where('code', $code)
->where('expires_at', '>', now())
->exists();
}
private function sendSms(string $phone, string $code): array
{
try {
$formattedRecipient = $this->formatPhoneForSms($phone);
$response = Http::withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Api-Key' => config('services.iranpayamak.api_key'),
])
->post(config('services.iranpayamak.api_url'), [
'code' => config('services.iranpayamak.patterns.verify_code'),
'attributes' => [
'otp' => $code,
'status' => 'بازنشانی رمز عبور'
],
'recipient' => $formattedRecipient,
'line_number' => config('services.iranpayamak.line_number'),
'number_format' => 'english',
]);
if ($response->successful()) {
return $response->json();
} else {
return [
'status' => 'error',
'message' => 'SMS sending failed: ' . $response->status(),
'data' => $response->body()
];
}
} catch (\Exception $e) {
return [
'status' => 'error',
'message' => 'SMS sending exception: ' . $e->getMessage(),
'data' => null
];
}
}
private function formatPhoneForSms(string $phone): string
{
if (str_starts_with($phone, '+98')) {
return '0' . substr($phone, 3);
}
return $phone;
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Services\Auth;
use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Auth;
class RegistrationService
{
public function createUser(array $data): User
{
return DB::transaction(function () use ($data) {
$user = User::create([
'username' => 'rdl_'.Str::random(10),
'phone' => $data['phone'],
'email' => $data['email'] ?? null,
'password' => Hash::make($data['password'] ?? Str::random(12)),
'level' => $data['level'],
'fullname' => $data['fullname'] ?? null,
'national_code' => $data['national_code'] ?? null,
'clinic_name' => $data['clinic_name'] ?? null,
'clinic_phone' => $data['clinic_phone'] ?? null,
'province' => $data['province'] ?? null,
'city' => $data['city'] ?? null,
'address' => $data['address'] ?? null,
'specialty' => $data['specialty'] ?? null,
]);
return $user;
});
}
public function registerAndLogin(array $data): Authenticatable
{
$user = $this->createUser($data);
Auth::login($user);
session()->regenerate();
session()->forget('register_phone' , 'verified_phone' , 'otp_verified' , 'login_phone' , 'login_level' , 'available_levels');
return $user;
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Services\Document;
use App\Models\Document;
use RuntimeException;
class DocumentCodeService
{
public function generate(int $tries = 10): string
{
for ($i = 0; $i < $tries; $i++) {
$code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
if (! Document::where('code', $code)->exists()) {
return $code;
}
}
throw new RuntimeException('Unable to generate unique code');
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace App\Services;
use App\Models\Medium;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class FileStorageService
{
public function storeFile(UploadedFile $file, string $disk = 'public', string $directory = 'files'): Medium
{
$diskInstance = Storage::disk($disk);
$filename = $this->generateUniqueFilename($file);
$path = $directory . '/' . $filename;
$visibility = $disk === 'public' ? 'public' : 'private';
DB::beginTransaction();
try {
$diskInstance->putFileAs($directory, $file, $filename, ['visibility' => $visibility]);
$medium = Medium::create([
'filename' => $filename,
'disk' => $disk,
'path' => $path,
'url' => $visibility === 'public' ? Storage::disk('public')->url($path) : null,
'size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
]);
DB::commit();
return $medium;
} catch (\Throwable $e) {
DB::rollBack();
if ($diskInstance->exists($path)) {
$diskInstance->delete($path);
}
throw $e;
}
}
public function moveToDisk(Medium $medium, string $newDisk): bool
{
if ($medium->disk->value === $newDisk) return true;
$old = Storage::disk($medium->disk->value);
$new = Storage::disk($newDisk);
$stream = $old->readStream($medium->path);
if ($stream === false) return false;
$new->put($medium->path, $stream);
if (is_resource($stream)) fclose($stream);
if ($newDisk === 'public') {
$new->setVisibility($medium->path, 'public');
$medium->url = Storage::disk('public')->url($medium->path);
} else {
$medium->url = null;
}
$old->delete($medium->path);
$medium->disk = $newDisk;
$medium->save();
return true;
}
public function deleteMedium(Medium $medium): mixed
{
$disk = Storage::disk($medium->disk->value);
if ($disk->exists($medium->path)) {
$disk->delete($medium->path);
}
return $medium->delete();
}
public function downloadFile(Medium $medium)
{
if (!$medium->isPrivate()) {
abort(404, 'فایل مورد نظر یافت نشد.');
}
if (!$medium->exists()) {
abort(404, 'فایل مورد نظر یافت نشد.');
}
return response()->download(
$medium->getStorageDisk()->path($medium->path),
$medium->filename
);
}
public function serveFile(Medium $medium)
{
if (!$medium->isPrivate()) {
abort(404, 'فایل مورد نظر یافت نشد.');
}
if (!$medium->exists()) {
abort(404, 'فایل مورد نظر یافت نشد.');
}
$file = $medium->getContents();
$mimeType = $medium->mime_type;
if (!$mimeType) {
$mimeType = mime_content_type($medium->getStorageDisk()->path($medium->path));
}
return response($file, 200, [
'Content-Type' => $mimeType,
'Content-Disposition' => 'inline; filename="' . $medium->filename . '"'
]);
}
private function generateUniqueFilename(UploadedFile $file): string
{
$ext = $file->getClientOriginalExtension();
$name = Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME));
return time() . '_' . Str::random(6) . '_' . $name . ($ext ? ".{$ext}" : '');
}
}