Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Hamidreza Ranjbarpour
2024-08-20 13:53:57 +03:30
18 changed files with 979 additions and 83 deletions

107
app/Facades/File/File.php Normal file
View File

@@ -0,0 +1,107 @@
<?php
namespace App\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\File as HttpFile;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
class File
{
public function save(HttpFile|UploadedFile $file, string $path, string $name = null, string $disk = 'public'): string
{
return Storage::disk($disk)->putFileAs(
$path,
$file,
$name ?? Str::uuid() . '.' . $file->extension()
);
}
/**
* @param string $filePath
* @param bool $url_is_absolute
* @param string $disk
* @return void
* @throws \InvalidArgumentException when filePath is empty.
*/
public function delete(string $filePath, bool $url_is_absolute = false, string $disk = 'public'): void
{
if (!$filePath){
throw new \InvalidArgumentException('File path is invalid.');
}
if ($url_is_absolute) {
$needle = 'storage/';
$pos = strpos($filePath, $needle);
$filePath = substr($filePath, $pos + strlen($needle));
}
if (Storage::disk($disk)->exists($filePath)) {
Storage::disk($disk)->delete($filePath);
}
}
public function deleteContent($file_path): bool
{
if (file_exists($file_path)) {
file_put_contents($file_path, '');
return true;
}
return false;
}
public function tail($file_path, $lines = 100, $buffer = 2048): bool|string
{
// Open file
$file = @fopen($file_path, "rb");
if ($file === false) {
return false;
}
// Jump to last character
fseek($file, -1, SEEK_END);
// Read it and adjust line number if necessary
// (Otherwise the result would be wrong if file doesn't end with a blank line)
if (fread($file, 1) != "\n") {
$lines -= 1;
}
// Start reading
$output = '';
$chunk = '';
// While we would like more
while (ftell($file) > 0 && $lines >= 0) {
// Figure out how far back we should jump
$seek = min(ftell($file), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($file, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$output = ($chunk = fread($file, $seek)) . $output;
// Jump back to where we started reading
fseek($file, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, "\n");
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {
// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);
}
// Close file and return
fclose($file);
return trim($output);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Facades\File;
use Illuminate\Support\Facades\Facade;
class FileFacade extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'file';
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\V3;
use App\Facades\File\FileFacade;
use App\Http\Controllers\Controller;
use App\Http\Requests\V3\Profile\ChangePasswordRequest;
use App\Http\Requests\V3\Profile\EditRequest;
use App\Http\Traits\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
use ApiResponse;
public function info(): JsonResponse
{
$user = Auth::user();
return $this->successResponse([
'username' => $user->username,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'name' => $user->name,
'last_login' => $user->last_login,
'major' => $user->major,
'degree' => $user->degree,
'mobile' => $user->mobile,
'avatar' => $user->avatar,
]);
}
public function edit(EditRequest $request): JsonResponse
{
$user = Auth::user();
if ($request->has('avatar'))
{
if ($user->avatar) {
FileFacade::delete($user->avatar);
}
$destinationPath = 'avatar/' . $user->username;
$filename = time() .".". $request->file('avatar')->getClientOriginalExtension();
$avatar = FileFacade::save($request->avatar, $destinationPath, $filename);
}
$user->update([
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'major' => $request->major,
'degree' => $request->degree,
'mobile' => $request->mobile,
'avatar' => $avatar ?? $user->avatar
]);
return $this->successResponse();
}
public function changePassword(ChangePasswordRequest $request): JsonResponse
{
$user = Auth::user();
if (! Hash::check($request->current_password, $user->password))
{
return $this->errorResponse(__('messages.incorrect_current_password'));
}
$user->update([
'password' => Hash::make($request->new_password)
]);
return $this->successResponse();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\V3\Profile;
use Illuminate\Foundation\Http\FormRequest;
class ChangePasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'current_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8',
'new_password' => 'required|string|regex:/^(?=.*[a-zA-Z])(?=.*\d).+$/|min:8',
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\V3\Profile;
use Illuminate\Foundation\Http\FormRequest;
class EditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'first_name' => 'required|string',
'last_name' => 'required|string',
'degree' => 'required',
'major' => 'required',
'mobile' => 'required|regex:/^09\d{9}$/|numeric|digits:11',
'avatar' => 'mimes:png,jpg,jpeg|max:2048'
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Providers;
use App\Facades\File\File;
use Illuminate\Support\ServiceProvider;
class FileServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register(): void
{
$this->app->bind('file', function () {
return new File();
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
}
}

View File

@@ -69,8 +69,8 @@ class RouteServiceProvider extends ServiceProvider
->group(base_path('routes/v2.php')); ->group(base_path('routes/v2.php'));
Route::prefix('api/v3') Route::prefix('api/v3')
->name('v3') ->name('v3.')
->middleware('web', 'auth', 'confirmUser') ->middleware(['web', 'auth', 'confirmUser'])
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/v3.php')); ->group(base_path('routes/v3.php'));
Route::prefix('api') Route::prefix('api')

View File

@@ -45,6 +45,7 @@
41 => 'App\\Providers\\TelescopeServiceProvider', 41 => 'App\\Providers\\TelescopeServiceProvider',
42 => 'Barryvdh\\Debugbar\\ServiceProvider', 42 => 'Barryvdh\\Debugbar\\ServiceProvider',
43 => 'App\\Providers\\DataTableServiceProvider', 43 => 'App\\Providers\\DataTableServiceProvider',
44 => 'App\\Providers\\FileServiceProvider',
), ),
'eager' => 'eager' =>
array ( array (
@@ -78,6 +79,7 @@
27 => 'App\\Providers\\TelescopeServiceProvider', 27 => 'App\\Providers\\TelescopeServiceProvider',
28 => 'Barryvdh\\Debugbar\\ServiceProvider', 28 => 'Barryvdh\\Debugbar\\ServiceProvider',
29 => 'App\\Providers\\DataTableServiceProvider', 29 => 'App\\Providers\\DataTableServiceProvider',
30 => 'App\\Providers\\FileServiceProvider',
), ),
'deferred' => 'deferred' =>
array ( array (

View File

@@ -183,6 +183,7 @@ return [
// custom facades here.. // custom facades here..
App\Providers\DataTableServiceProvider::class, App\Providers\DataTableServiceProvider::class,
App\Providers\FileServiceProvider::class,
], ],

View File

@@ -3,6 +3,7 @@
namespace Database\Factories; namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
@@ -23,6 +24,8 @@ class UserFactory extends Factory
'confirmed' => 1, 'confirmed' => 1,
'province_fa' => $this->faker->name, 'province_fa' => $this->faker->name,
'city_fa' => $this->faker->name, 'city_fa' => $this->faker->name,
'password' => Hash::make('password1234'),
'avatar' => $this->faker->image
]; ];
} }
} }

View File

@@ -0,0 +1,6 @@
<?php
return [
'successful' => 'The Operation was successful',
'incorrect_current_password' => 'The current password is incorrect.',
];

View File

@@ -0,0 +1,6 @@
<?php
return [
'successful' => 'عملیات موفق آمیز بود',
'incorrect_current_password' => 'رمز عبور فعلی اشتباه است',
];

View File

@@ -13,109 +13,109 @@ return [
| |
*/ */
"accepted" => ":attribute باید پذیرفته شده باشد.", "accepted" => ":attribute باید پذیرفته شده باشد.",
"active_url" => "آدرس :attribute معتبر نیست", "active_url" => "آدرس :attribute معتبر نیست",
"after" => ":attribute باید بعد از :date باشد.", "after" => ":attribute باید بعد از :date باشد.",
'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.', 'after_or_equal' => ':attribute باید بعد از یا برابر تاریخ :date باشد.',
"alpha" => ":attribute باید شامل حروف الفبا باشد.", "alpha" => ":attribute باید شامل حروف الفبا باشد.",
"alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.", "alpha_dash" => ":attribute باید شامل حروف الفبا و عدد و خظ تیره(-) باشد.",
"alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.", "alpha_num" => ":attribute باید شامل حروف الفبا و عدد باشد.",
"array" => ":attribute باید شامل آرایه باشد.", "array" => ":attribute باید شامل آرایه باشد.",
"before" => ":attribute باید تاریخی قبل از :date باشد.", "before" => ":attribute باید تاریخی قبل از :date باشد.",
'before_or_equal' => ':attribute باید قبل از یا برابر تاریخ :date باشد.', 'before_or_equal' => ':attribute باید قبل از یا برابر تاریخ :date باشد.',
"between" => [ "between" => [
"numeric" => ":attribute باید بین :min و :max باشد.", "numeric" => ":attribute باید بین :min و :max باشد.",
"file" => ":attribute باید بین :min و :max کیلوبایت باشد.", "file" => ":attribute باید بین :min و :max کیلوبایت باشد.",
"string" => ":attribute باید بین :min و :max کاراکتر باشد.", "string" => ":attribute باید بین :min و :max کاراکتر باشد.",
"array" => ":attribute باید بین :min و :max آیتم باشد.", "array" => ":attribute باید بین :min و :max آیتم باشد.",
], ],
"boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد", "boolean" => "فیلد :attribute فقط میتواند صحیح و یا غلط باشد",
"confirmed" => ":attribute با تاییدیه مطابقت ندارد.", "confirmed" => ":attribute با تاییدیه مطابقت ندارد.",
"date" => ":attribute یک تاریخ معتبر نیست.", "date" => ":attribute یک تاریخ معتبر نیست.",
'date_equals' => ':attribute باید برابر تاریخ :date باشد.', 'date_equals' => ':attribute باید برابر تاریخ :date باشد.',
"date_format" => ":attribute با الگوی :format مطاقبت ندارد.", "date_format" => ":attribute با الگوی :format مطاقبت ندارد.",
"different" => ":attribute و :other باید متفاوت باشند.", "different" => ":attribute و :other باید متفاوت باشند.",
"digits" => ":attribute باید :digits رقم باشد.", "digits" => ":attribute باید :digits رقم باشد.",
"digits_between" => ":attribute باید بین :min و :max رقم باشد.", "digits_between" => ":attribute باید بین :min و :max رقم باشد.",
'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.', 'dimensions' => 'dimensions مربوط به فیلد :attribute اشتباه است.',
'distinct' => ':attribute مقدار تکراری دارد.', 'distinct' => ':attribute مقدار تکراری دارد.',
"email" => "فرمت :attribute معتبر نیست.", "email" => "فرمت :attribute معتبر نیست.",
'ends_with' => ':attribute باید با این مقدار تمام شود: :values.', 'ends_with' => ':attribute باید با این مقدار تمام شود: :values.',
"exists" => ":attribute انتخاب شده، معتبر نیست.", "exists" => ":attribute انتخاب شده، معتبر نیست.",
'file' => 'فیلد :attribute باید فایل باشد.', 'file' => 'فیلد :attribute باید فایل باشد.',
"filled" => "فیلد :attribute الزامی است", "filled" => "فیلد :attribute الزامی است",
'gt' => [ 'gt' => [
'numeric' => ':attribute باید بیشتر از :value باشد.', 'numeric' => ':attribute باید بیشتر از :value باشد.',
'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.', 'file' => ':attribute باید بیشتر از :value کیلوبایت باشد.',
'string' => ':attribute باید بیشتر از :value کاراکتر باشد.', 'string' => ':attribute باید بیشتر از :value کاراکتر باشد.',
'array' => ':attribute باید بیشتر از :value ایتم باشد.', 'array' => ':attribute باید بیشتر از :value ایتم باشد.',
], ],
'gte' => [ 'gte' => [
'numeric' => ':attribute باید بیشتر یا برابر :value باشد.', 'numeric' => ':attribute باید بیشتر یا برابر :value باشد.',
'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.', 'file' => ':attribute باید بیشتر یا برابر :value کیلوبایت باشد.',
'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.', 'string' => ':attribute باید بیشتر یا برابر :value کاراکتر باشد.',
'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.', 'array' => ':attribute باید :value ایتم یا بیشتر را داشته باشد.',
], ],
"image" => ":attribute باید تصویر باشد.", "image" => ":attribute باید تصویر باشد.",
"in" => ":attribute انتخاب شده، معتبر نیست.", "in" => ":attribute انتخاب شده، معتبر نیست.",
"integer" => ":attribute باید نوع داده ای عددی (integer) باشد.", "integer" => ":attribute باید نوع داده ای عددی (integer) باشد.",
"ip" => ":attribute باید IP آدرس معتبر باشد.", "ip" => ":attribute باید IP آدرس معتبر باشد.",
'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.', 'ipv4' => ':attribute باید یک ادرس درست IPv4 باشد.',
'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.', 'ipv6' => ':attribute باید یک ادرس درست IPv6 باشد.',
'json' => ':attribute یک مقدار درست JSON باشد.', 'json' => ':attribute یک مقدار درست JSON باشد.',
'lt' => [ 'lt' => [
'numeric' => ':attribute باید کمتر از :value باشد.', 'numeric' => ':attribute باید کمتر از :value باشد.',
'file' => ':attribute باید کمتر از :value کیلوبایت باشد.', 'file' => ':attribute باید کمتر از :value کیلوبایت باشد.',
'string' => ':attribute باید کمتر از :value کاراکتر باشد.', 'string' => ':attribute باید کمتر از :value کاراکتر باشد.',
'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.',
], ],
'lte' => [ 'lte' => [
'numeric' => ':attribute باید کمتر یا برابر :value باشد.', 'numeric' => ':attribute باید کمتر یا برابر :value باشد.',
'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.', 'file' => ':attribute باید کمتر یا برابر :value کیلوبایت باشد.',
'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.', 'string' => ':attribute باید کمتر یا برابر :value کاراکتر باشد.',
'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.', 'array' => ':attribute باید :value ایتم یا کمتر را داشته باشد.',
], ],
"max" => [ "max" => [
"numeric" => ":attribute نباید بزرگتر از :max باشد.", "numeric" => ":attribute نباید بزرگتر از :max باشد.",
"file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.", "file" => ":attribute نباید بزرگتر از :max کیلوبایت باشد.",
"string" => ":attribute نباید بیشتر از :max کاراکتر باشد.", "string" => ":attribute نباید بیشتر از :max کاراکتر باشد.",
"array" => ":attribute نباید بیشتر از :max آیتم باشد.", "array" => ":attribute نباید بیشتر از :max آیتم باشد.",
], ],
"mimes" => ":attribute باید یکی از فرمت های :values باشد.", "mimes" => ":attribute باید یکی از فرمت های :values باشد.",
'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.', 'mimetypes' => ':attribute باید تایپ ان از نوع: :values باشد.',
"min" => [ "min" => [
"numeric" => ":attribute نباید کوچکتر از :min باشد.", "numeric" => ":attribute نباید کوچکتر از :min باشد.",
"file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.", "file" => ":attribute نباید کوچکتر از :min کیلوبایت باشد.",
"string" => ":attribute نباید کمتر از :min کاراکتر باشد.", "string" => ":attribute نباید کمتر از :min کاراکتر باشد.",
"array" => ":attribute نباید کمتر از :min آیتم باشد.", "array" => ":attribute نباید کمتر از :min آیتم باشد.",
], ],
"not_in" => ":attribute انتخاب شده، معتبر نیست.", "not_in" => ":attribute انتخاب شده، معتبر نیست.",
'not_regex' => ':attribute فرمت معتبر نیست.', 'not_regex' => ':attribute فرمت معتبر نیست.',
"numeric" => ":attribute باید شامل عدد باشد.", "numeric" => ":attribute باید شامل عدد باشد.",
'password' => 'رمز عبور اشتباه است.', 'password' => 'رمز عبور اشتباه است.',
'present' => ':attribute باید وجود داشته باشد.', 'present' => ':attribute باید وجود داشته باشد.',
"regex" => ":attribute یک فرمت معتبر نیست", "regex" => ":attribute یک فرمت معتبر نیست",
"required" => "فیلد :attribute الزامی است", "required" => "فیلد :attribute الزامی است",
"required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.", "required_if" => "فیلد :attribute هنگامی که :other برابر با :value است، الزامیست.",
'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.', 'required_unless' => 'قیلد :attribute الزامیست مگر این فیلد :other مقدارش :values باشد.',
"required_with" => ":attribute الزامی است زمانی که :values موجود است.", "required_with" => ":attribute الزامی است زمانی که :values موجود است.",
"required_with_all" => ":attribute الزامی است زمانی که :values موجود است.", "required_with_all" => ":attribute الزامی است زمانی که :values موجود است.",
"required_without" => ":attribute الزامی است زمانی که :values موجود نیست.", "required_without" => ":attribute الزامی است زمانی که :values موجود نیست.",
"required_without_all" => ":attribute الزامی است زمانی که :values موجود نیست.", "required_without_all" => ":attribute الزامی است زمانی که :values موجود نیست.",
"same" => ":attribute و :other باید مانند هم باشند.", "same" => ":attribute و :other باید مانند هم باشند.",
"size" => [ "size" => [
"numeric" => ":attribute باید برابر با :size باشد.", "numeric" => ":attribute باید برابر با :size باشد.",
"file" => ":attribute باید برابر با :size کیلوبایت باشد.", "file" => ":attribute باید برابر با :size کیلوبایت باشد.",
"string" => ":attribute باید برابر با :size کاراکتر باشد.", "string" => ":attribute باید برابر با :size کاراکتر باشد.",
"array" => ":attribute باسد شامل :size آیتم باشد.", "array" => ":attribute باسد شامل :size آیتم باشد.",
], ],
'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.', 'starts_with' => ':attribute باید با یکی از این مقادیر شروع شود: :values.',
"string" => ":attribute باید رشته باشد.", "string" => ":attribute باید رشته باشد.",
"timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.", "timezone" => "فیلد :attribute باید یک منطقه صحیح باشد.",
"unique" => ":attribute قبلا انتخاب شده است.", "unique" => ":attribute قبلا انتخاب شده است.",
'uploaded' => 'فیلد :attribute به درستی اپلود نشد.', 'uploaded' => 'فیلد :attribute به درستی اپلود نشد.',
"url" => "فرمت آدرس :attribute اشتباه است.", "url" => "فرمت آدرس :attribute اشتباه است.",
'uuid' => ':attribute باید یک فرمت درست UUID باشد.', 'uuid' => ':attribute باید یک فرمت درست UUID باشد.',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -173,7 +173,12 @@ return [
"time" => "زمان", "time" => "زمان",
"available" => "موجود", "available" => "موجود",
"size" => "اندازه", "size" => "اندازه",
"file" => "فایل", "file" => "فایل",
"fullname" => "نام کامل" "fullname" => "نام کامل",
'current_password' => 'رمز عبور فعلی',
'new_password' => 'رمز عبور جدید',
'degree' => 'مدرک تحصیلی',
'major' => 'شغل',
'avatar' => 'آواتار'
], ],
]; ];

View File

@@ -1,6 +1,8 @@
<?php <?php
use App\Http\Controllers\V3\Harim\DivarkeshiController; use App\Http\Controllers\V3\Harim\DivarkeshiController;
use App\Http\Controllers\V3\ProfileController;
use Illuminate\Support\Facades\Route;
Route::prefix('harim')->name('harim')->group(function () { Route::prefix('harim')->name('harim')->group(function () {
Route::prefix('divarkeshi')->name('divarkeshi')->controller(DivarkeshiController::class)->group(function () { Route::prefix('divarkeshi')->name('divarkeshi')->controller(DivarkeshiController::class)->group(function () {
@@ -21,3 +23,12 @@ Route::prefix('harim')->name('harim')->group(function () {
// Route::get('/{contract}', 'V3\Dashboard\ContractController@detail')->name('V3.contract.detail'); // Route::get('/{contract}', 'V3\Dashboard\ContractController@detail')->name('V3.contract.detail');
}); });
}); });
Route::prefix('profile')
->name('profile.')
->controller(ProfileController::class)
->group(function () {
Route::get('info', 'info')->name('info');
Route::post('edit', 'edit')->name('edit');
Route::post('change_password', 'changePassword')->name('changePassword');
});

View File

@@ -0,0 +1,115 @@
<?php
namespace Tests\Feature\Facades;
use App\Facades\File\FileFacade;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class FileTest extends TestCase
{
use WithFaker;
/**
* A basic feature test example.
*/
public function test_image_is_saved(): void
{
Storage::fake('public');
$inputFile = UploadedFile::fake()->image($this->faker->image);
$path = FileFacade::save($inputFile, 'testDir');
Storage::disk('public')->assertExists($path);
}
public function test_image_is_saved_with_specified_name(): void
{
Storage::fake('public');
$imageName = 'photo.png';
$inputFile = UploadedFile::fake()->image($imageName);
$actualPath = FileFacade::save($inputFile, 'testDir', $imageName);
$expectedPath = 'testDir/photo.png';
Storage::disk('public')->assertExists($actualPath);
$this->assertEquals($expectedPath, $actualPath);
}
public function test_file_is_saved(): void
{
Storage::fake('public');
$fileName = 'fake' . '.' . $this->faker->fileExtension();
$inputFile = UploadedFile::fake()->create($fileName);
$path = FileFacade::save($inputFile, 'testDir');
Storage::disk('public')->assertExists($path);
}
public function test_file_is_saved_with_specified_name(): void
{
Storage::fake('public');
$fileName = 'fake' . '.' . $this->faker->fileExtension();
$inputFile = UploadedFile::fake()->create($fileName);
$actualPath = FileFacade::save($inputFile, 'testDir', $fileName);
$expectedPath = 'testDir/' . $fileName;
Storage::disk('public')->assertExists($actualPath);
$this->assertEquals($expectedPath, $actualPath);
}
public function test_file_is_saved_with_specified_disk(): void
{
Storage::fake('public');
Storage::fake('p2');
$fileName = 'fake' . '.' . $this->faker->fileExtension();
$inputFile = UploadedFile::fake()->create($fileName);
$path = FileFacade::save($inputFile, 'testDir', disk: 'p2');
Storage::disk('p2')->assertExists($path);
Storage::disk('public')->assertMissing($path);
}
public function test_delete_throws_exception_with_empty_file_path(): void
{
$this->assertThrows(
fn() => FileFacade::delete(''),
\InvalidArgumentException::class
);
}
public function test_file_is_deleted(): void
{
Storage::fake('public');
$fileName = 'fake' . '.' . $this->faker->fileExtension();
$inputFile = UploadedFile::fake()->create($fileName);
$path = Storage::disk('public')->putFile('testDir', $inputFile);
Storage::disk('public')->assertExists($path);
FileFacade::delete($path);
Storage::disk('public')->assertMissing($path);
}
public function test_file_with_absolute_path_is_deleted(): void
{
Storage::fake('public');
$fileName = 'fake' . '.' . $this->faker->fileExtension();
$inputFile = UploadedFile::fake()->create($fileName);
$path = Storage::disk('public')->putFile('testDir', $inputFile);
$absolutePath = Storage::disk('public')->url($path);
Storage::disk('public')->assertExists($path);
FileFacade::delete($absolutePath, url_is_absolute: true);
Storage::disk('public')->assertMissing($path);
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace Tests\Feature\V3\Profile;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class ChangePasswordTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function test_current_password_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'));
$response->assertUnprocessable()
->assertInvalid([
'current_password' => __('validation.required', ['attribute' => 'رمز عبور فعلی'])
]);
}
public function test_current_password_should_be_string()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'current_password' => $this->faker->numberBetween(1, 10)
]);
$response->assertUnprocessable()
->assertInvalid([
'current_password' => __('validation.string', ['attribute' => 'رمز عبور فعلی'])
]);
}
public function test_current_password_should_match_the_regex()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'current_password' => $this->faker->numberBetween(10000000, 99999999)
]);
$response->assertUnprocessable()
->assertInvalid([
'current_password' => __('validation.regex', ['attribute' => 'رمز عبور فعلی'])
]);
}
public function test_current_password_should_have_at_least_8_characters()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'current_password' => $this->faker->numerify()
]);
$response->assertUnprocessable()
->assertInvalid([
'current_password' => __('validation.min.string', ['attribute' => 'رمز عبور فعلی', 'min' => 8])
]);
}
public function test_new_password_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'));
$response->assertUnprocessable()
->assertInvalid([
'new_password' => __('validation.required', ['attribute' => 'رمز عبور جدید'])
]);
}
public function test_new_password_should_be_string()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'new_password' => $this->faker->numberBetween(1, 10)
]);
$response->assertUnprocessable()
->assertInvalid([
'new_password' => __('validation.string', ['attribute' => 'رمز عبور جدید'])
]);
}
public function test_new_password_should_match_the_regex()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'new_password' => $this->faker->numberBetween(10000000, 99999999)
]);
$response->assertUnprocessable()
->assertInvalid([
'new_password' => __('validation.regex', ['attribute' => 'رمز عبور جدید'])
]);
}
public function test_new_password_should_have_at_least_8_characters()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.changePassword'), [
'new_password' => $this->faker->numerify()
]);
$response->assertUnprocessable()
->assertInvalid([
'new_password' => __('validation.min.string', ['attribute' => 'رمز عبور جدید', 'min' => 8])
]);
}
public function test_can_not_change_the_password_of_the_user_with_wrong_current_password(): void
{
$this->withoutExceptionHandling();
$user = User::factory()->create();
$this->actingAs($user);
$password = $this->faker->numerify('#a######');
$response = $this->post(route('v3.profile.changePassword'), [
'current_password' => 'fake1234',
'new_password' => $password,
]);
$response->assertStatus(422);
$response->assertExactJson([
'message' => __('messages.incorrect_current_password'),
"type" => "logical_exception"
]);
}
public function test_user_can_change_the_password(): void
{
$this->withoutExceptionHandling();
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post(route('v3.profile.changePassword'), [
'current_password' => 'password1234',
'new_password' => $this->faker->numerify('#a######'),
]);
$response->assertStatus(200);
$response->assertExactJson([
'message' => __('messages.successful')
]);
}
}

View File

@@ -0,0 +1,239 @@
<?php
namespace Tests\Feature\V3\Profile;
use App\Facades\File\FileFacade;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class EditTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function test_first_name_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'));
$response->assertUnprocessable()
->assertInvalid([
'first_name' => __('validation.required', ['attribute' => 'نام'])
]);
}
public function test_first_name_should_be_string()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'), [
'first_name' => $this->faker->numberBetween(0, 10)
]);
$response->assertUnprocessable()
->assertInvalid([
'first_name' => __('validation.string', ['attribute' => 'نام'])
]);
}
public function test_last_name_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'));
$response->assertUnprocessable()
->assertInvalid([
'last_name' => __('validation.required', ['attribute' => 'نام خانوادگی'])
]);
}
public function test_last_name_should_be_string()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'), [
'last_name' => $this->faker->numberBetween(0, 10)
]);
$response->assertUnprocessable()
->assertInvalid([
'last_name' => __('validation.string', ['attribute' => 'نام خانوادگی'])
]);
}
public function test_degree_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'));
$response->assertUnprocessable()
->assertInvalid([
'degree' => __('validation.required', ['attribute' => 'مدرک تحصیلی'])
]);
}
public function test_major_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'));
$response->assertUnprocessable()
->assertInvalid([
'major' => __('validation.required', ['attribute' => 'شغل'])
]);
}
public function test_mobile_is_required()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'));
$response->assertUnprocessable()
->assertInvalid([
'mobile' => __('validation.required', ['attribute' => 'تلفن همراه'])
]);
}
public function test_mobile_should_match_the_regex()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'), [
'mobile' => $this->faker->numberBetween(1000000, 9999999)
]);
$response->assertUnprocessable()
->assertInvalid([
'mobile' => __('validation.regex', ['attribute' => 'تلفن همراه'])
]);
}
public function test_mobile_should_be_numeric()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'), [
'mobile' => $this->faker->name()
]);
$response->assertUnprocessable()
->assertInvalid([
'mobile' => __('validation.numeric', ['attribute' => 'تلفن همراه'])
]);
}
public function test_mobile_should_be_11_digits()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->postJson(route('v3.profile.edit'), [
'mobile' => $this->faker->numberBetween(10000, 99999)
]);
$response->assertUnprocessable()
->assertInvalid([
'mobile' => __('validation.digits', ['attribute' => 'تلفن همراه', 'digits' => 11])
]);
}
public function test_avatar_should_match_the_mimes()
{
$user = User::factory()->create();
$this->actingAs($user);
$image = UploadedFile::fake()->create('image.txt', 40);
$response = $this->postJson(route('v3.profile.edit'), [
'avatar' => $image
]);
$response->assertUnprocessable()
->assertInvalid([
'avatar' => __('validation.mimes', ['attribute' => 'آواتار', 'values' => 'png, jpg, jpeg'])
]);
}
public function test_avatar_should_be_less_than_2048kb()
{
$user = User::factory()->create();
$this->actingAs($user);
$image = UploadedFile::fake()->create('image.jpg', 40000);
$response = $this->postJson(route('v3.profile.edit'), [
'avatar' => $image
]);
$response->assertUnprocessable()
->assertInvalid([
'avatar' => __('validation.max.file', ['attribute' => 'آواتار', 'max' => 2048])
]);
}
public function test_user_can_edit_the_profile()
{
$user = User::factory()->create();
$this->actingAs($user);
$avatar = UploadedFile::fake()->create('image.jpg', 1024);
FileFacade::shouldReceive('save')
->andReturn('image.jpg');
FileFacade::shouldReceive('delete');
$data = [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'degree' => $this->faker->name,
'major' => $this->faker->name,
'mobile' => $this->faker->numerify('09#########'),
'avatar' => $avatar
];
$response = $this->postJson(route('v3.profile.edit'), $data);
$response->assertOk()
->assertExactJson([
'message' => __('messages.successful')
]);
$this->assertDatabaseHas('users', [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'mobile' => $data['mobile'],
'degree' => $data['degree'],
]);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\V3\Profile;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class InfoTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function test_user_info_returns_successfully(): void
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get(route('v3.profile.info'));
$response->assertStatus(200);
$response->assertExactJson([
'data' => [
'username' => $user->username,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'name' => $user->name,
'last_login' => $user->last_login,
'major' => $user->major,
'degree' => $user->degree,
'mobile' => $user->mobile,
'avatar' => $user->avatar,
]
]);
}
}