From 9c22aa2b12754babd765c10902c9f5f5b65f704b Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Wed, 3 Mar 2021 17:10:10 +0330 Subject: [PATCH 01/11] Fix missing vars in method definition --- app/Http/Controllers/ProjectController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 4a396ef..00a20cb 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -22,7 +22,7 @@ class ProjectController extends Controller return Business::info($request->route('business'), true); } - public function update(Request $request,string $project) + public function update(Request $request, int $business, string $project) { permit('projectEdit', ['project_id' => $project]); $project = Project::findOrFail($project); @@ -31,7 +31,7 @@ class ProjectController extends Controller } - public function delete(Request $request, string $project) + public function delete(Request $request, int $business, string $project) { permit('businessProjects'); $project = Project::findOrFail($project); @@ -39,7 +39,7 @@ class ProjectController extends Controller return Business::info($request->route('business')); } - public function restore(Request $request, string $project) + public function restore(Request $request, int $business, string $project) { $project = Project::onlyTrashed()->findOrFail($project); $project->restore(); @@ -139,7 +139,7 @@ class ProjectController extends Controller } } - public function setAvatar(Request $request, string $project) + public function setAvatar(Request $request, int $business, string $project) { $project = Project::findOrFail($project); if ($request->hasFile('avatar')) { @@ -149,7 +149,7 @@ class ProjectController extends Controller return $project; } - public function unSetAvatar(Request $request, string $project) + public function unSetAvatar(Request $request,int $business ,string $project) { $project = Project::findOrFail($project); $project->deleteAvatar(); From 6c868508eea6ec38273ed786301ba8c73bd16d26 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Wed, 3 Mar 2021 17:47:26 +0330 Subject: [PATCH 02/11] Fix zarinpal --- config/app.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/app.php b/config/app.php index cc438b2..7f44ebe 100644 --- a/config/app.php +++ b/config/app.php @@ -64,6 +64,7 @@ return [ App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Utilities\Zarinpal\Laravel\ZarinpalServiceProvider::class, ], 'aliases' => [ @@ -104,5 +105,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, + + 'View' => App\Utilities\Zarinpal\Laravel\Facade\Zarinpal::class, ], ]; From 2c0e162bf40a2b85ea3c8510bcb2444cf16e50ce Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Wed, 3 Mar 2021 18:16:51 +0330 Subject: [PATCH 03/11] Fix task file --- app/Http/Controllers/TaskFileController.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/TaskFileController.php b/app/Http/Controllers/TaskFileController.php index 6750715..b8bd77c 100644 --- a/app/Http/Controllers/TaskFileController.php +++ b/app/Http/Controllers/TaskFileController.php @@ -11,7 +11,6 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Http\Resources\FileResource; use Symfony\Component\HttpFoundation\Response; -use Illuminate\Http\Exceptions\HttpResponseException; class TaskFileController extends Controller { @@ -41,7 +40,7 @@ class TaskFileController extends Controller // guest or de active // return files as file resource [$business, $project, $task] = $this->checkBelonging($business, $project, $task); - return FileResource::collection($task->files); + return FileResource::collection($task->files ?? []); } public function sync(Request $request,int $business, int $project, int $task) @@ -78,7 +77,7 @@ class TaskFileController extends Controller // return the file resource or stream it [$business, $project, $task] = $this->checkBelonging($business, $project, $task); - $file = File::find($file); + $file = File::findOrFail($file); if ($file->user_id !== Auth::id()) { abort(Response::HTTP_UNAUTHORIZED); } From fc1bd902c4cf3c019a4129a11697efd1686717d8 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Wed, 3 Mar 2021 18:45:54 +0330 Subject: [PATCH 04/11] Fix max bound --- app/Http/Controllers/FileController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index 63816f6..a132732 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -4,7 +4,7 @@ namespace App\Http\Controllers; use App\Models\File; use App\Models\Project; -use App\Rules\maxBound; +use App\Rules\MaxBound; use App\Models\Business; use Illuminate\Support\Str; use Illuminate\Http\Request; @@ -102,7 +102,7 @@ class FileController extends Controller $business = Business::findOrFail($business); $project = Project::findOrFail($project); - // $this->validate($request, ['file' => 'required|file',]); + $this->validate($request, ['file' => 'required|file',]); $file = $request->file('file'); $file_extension = $file->getClientOriginalExtension(); From d91bebb9c7b636e6eab80b38781e7ee0a20be8e5 Mon Sep 17 00:00:00 2001 From: mahdihty Date: Sat, 6 Mar 2021 11:49:43 +0330 Subject: [PATCH 05/11] add middleware --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index a51789c..4a40912 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,7 +16,7 @@ $router->get('/{transaction}/redirection', 'CreditController@redirection'); $router->group(['prefix' => 'auth'], function () use ($router) { $router->get('/', 'AuthController@auth')->middleware('auth:api'); $router->delete('/', 'AuthController@delete'); - $router->get('/info', 'AuthController@authWithInfo'); + $router->get('/info', 'AuthController@authWithInfo')->middleware('auth:api'); $router->post('login', 'AuthController@login'); $router->post('logout', 'AuthController@logout')->middleware('auth:api'); $router->post('register', 'AuthController@register'); From 5fb0a4335ba25d7ca786e27b23c0f0aef8ea20c5 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Sat, 6 Mar 2021 18:57:30 +0330 Subject: [PATCH 06/11] Bug fix and improvments of cost work --- app/Console/Commands/CostCommand.php | 203 +++++++++++------- config/app.php | 2 +- database/factories/BusinessFactory.php | 2 +- ...0_08_18_085018_create_businesses_table.php | 2 +- 4 files changed, 123 insertions(+), 86 deletions(-) diff --git a/app/Console/Commands/CostCommand.php b/app/Console/Commands/CostCommand.php index 99a9db5..9845050 100644 --- a/app/Console/Commands/CostCommand.php +++ b/app/Console/Commands/CostCommand.php @@ -2,9 +2,12 @@ namespace App\Console\Commands; -use DB; +use Throwable; +use Carbon\Carbon; use App\Models\Business; use Illuminate\Console\Command; +use Morilog\Jalali\CalendarUtils; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Cache; class CostCommand extends Command @@ -28,109 +31,143 @@ class CostCommand extends Command public function handle() { - // infinte loop while (true) { - // to baghali ha - $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); - - $calculated_at = Cache::get('calculated_at'); - if ($calculated_at === null) { - $business = Business::orderBy('calculated_at')->first()->load('users', 'cost'); - $calculated_at = $business->calculated_at; - $until_now = jdate()->getMonth() > jdate($business->calculated_at)->getMonth() - ? jdate($business->calculated_at)->toCarbon()->setTime("00", "00", "00") - : \Carbon\Carbon::now(); - - Cache::put('calculated_at', $until_now, 60); + $business = Business::find(221); + if ($business === null) { + continue; + } + + $year = jdate()->getYear(); + $month = jdate()->getMonth(); + $day = jdate()->getDay(); + $hour = jdate()->getHour(); + $minute = jdate()->getMinute(); + $second = jdate()->getSecond(); + + if (jdate()->getYear() > jdate($business->calculated_at)->getYear()) { + $year = jdate()->getYear(); + $month = 1; + $day = 1; } + if ( + jdate()->getYear() > jdate($business->calculated_at)->getYear() + && + jdate()->getMonth() > jdate($business->calculated_at)->getMonth() + ) { + $year = jdate()->getYear(); + $month = jdate()->getMonth(); + $day = 1; + } + + $gDate = CalendarUtils::toGregorian($year, $month, $day); + $carbon = Carbon::createFromDate($gDate[0], $gDate[1], $gDate[2]); + $carbon->setTime($hour, $minute, $second); + + $now = $carbon; + // if calculated_at less than an hour stop - if (\Carbon\Carbon::now()->diffInMinutes($until_now) <= 60) { - $this->info('nothing to cost'); + if ($now->diffInMinutes($business->calculated_at)) { + $this->info('Must be one hour after the last audit.'); continue; } try { DB::beginTransaction(); + // Fixed amounts of expenses + $business->load('users', 'cost'); - // business order by last_calculated_at take first - if (!isset($business)) { - $business = Business::orderBy('calculated_at')->first()->load('users', 'cost'); - } - - $user_fee = enum('business.fee.user'); - - // get business employee - $users_cost = $business->cost - ->where('type', '=', 'users') - ->where('fee', '=', $user_fee) - ->where('month', '=', $recorded_month) - ->where('amount', '=', $business->users->count()) - ->first(); - - if ($users_cost === null) { - $business->cost()->create([ - 'type' => 'users', - 'month' => $recorded_month, - 'amount' => $business->users->count(), - 'fee' => $user_fee, - 'duration' => $duration = $until_now->diffInSeconds($calculated_at), // from the created_at time of the newset fifth user - 'additional' => $business->users->pluck('id')->toArray(), - ]); - } else { - $users_cost->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month), - 'additional' => $business->users->pluck('id')->toArray(), - ]); - } - - $costs = $user_fee * $duration; - - // do the math in php - if (intdiv($business->files_volume, 200) === 0) { - $pads = 0; - } else { - $pads = intdiv($business->files_volume, 200) - 1; - } - - $file_fee = enum('business.fee.file'); - - $files = $business->cost - ->where('type', '=', 'files') - ->where('fee', '=', $file_fee) - ->where('month', '=', $recorded_month) - ->where('amount', '=', $business->files_volume) - ->first(); - - if ($files === null) { - $business->cost()->create([ - 'type' => 'files', - 'month' => $recorded_month, - 'amount' => $pads, - 'fee' => $file_fee, - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // how to determine the file?, - ]); - } else { - $files->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month),, - ]); - } - - $costs += $file_fee * $duration; + $costs = 0; + $costs += $this->calculateCostOfBusinessUsers($business, $now); + $costs += $this->calculateCostOfBusinessFiles($business, $now); // increment and decrement of wallet in php // deduct costs from your business wallet // make sure save the calculated_at $business->update([ 'wallet' => $business->wallet - $costs, - 'calculated_at' => \Carbon\Carbon::now(), + 'calculated_at' => Carbon::now(), ]); + DB::commit(); - } catch (Throwable $thr) { + } catch (Throwable $throwable) { DB::rollback(); - throw $thr; + report($throwable); continue; } } } + + public function calculateCostOfBusinessUsers($business, $until_now) + { + $user_fee = enum('business.fee.user'); + $calculated_at = $business->calculated_at; + $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); + + + // get business employee + $users_cost = $business->cost + ->where('type', '=', 'users') + ->where('fee', '=', $user_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $business->users->count()) + ->first(); + + if ($users_cost === null) { + $business->cost()->create([ + 'type' => 'users', + 'month' => $recorded_month, + 'amount' => $business->users->count(), + 'fee' => $user_fee, + 'duration' => $duration = $until_now->diffInSeconds($calculated_at), // from the created_at time of the newset fifth user + 'additional' => $business->users->pluck('id')->toArray(), + ]); + } else { + $users_cost->update([ + 'duration' => $duration = $until_now->diffInMinutes($calculated_at) + $users_cost->duration, // last calc - (current month - now else last calc - end of the past month), + 'additional' => $business->users->pluck('id')->toArray(), + ]); + } + + return $user_fee * $duration; + } + + public function calculateCostOfBusinessFiles($business, $until_now) + { + $file_fee = enum('business.fee.file'); + $calculated_at = $business->calculated_at; + $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); + + + // do the math in php + if (intdiv($business->files_volume, 200) === 0) { + $pads = 0; + } else { + $pads = intdiv($business->files_volume, 200) - 1; + } + + + $files = $business->cost + ->where('type', '=', 'files') + ->where('fee', '=', $file_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $business->files_volume) + ->first(); + + if ($files === null) { + $business->cost()->create([ + 'type' => 'files', + 'month' => $recorded_month, + 'amount' => $pads, + 'fee' => $file_fee, + 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // how to determine the file?, + ]); + } else { + $files->update([ + 'duration' => $duration = $until_now->diffInMinutes($calculated_at) + $files->duration, // last calc - (current month - now else last calc - end of the past month),, + ]); + } + + return $file_fee * $duration; + } } diff --git a/config/app.php b/config/app.php index 7f44ebe..d517cdc 100644 --- a/config/app.php +++ b/config/app.php @@ -12,7 +12,7 @@ return [ 'asset_url' => env('ASSET_URL', null), - 'timezone' => 'UTC', + 'timezone' => 'Asia/Tehran', 'locale' => 'fa', diff --git a/database/factories/BusinessFactory.php b/database/factories/BusinessFactory.php index f8dab3d..28f0dae 100644 --- a/database/factories/BusinessFactory.php +++ b/database/factories/BusinessFactory.php @@ -12,6 +12,6 @@ $factory->define(Business::class, function (Faker $faker) { 'slug' => Str::slug($name) . $faker->numberBetween(1, 100), 'wallet' => random_int(111111, 999999), 'color' => $faker->colorName, - 'calculated_at' => \Carbon\Carbon::now()->subMinutes(random_int(1, 1000)), + 'calculated_at' => \Carbon\Carbon::now()->subDays(random_int(1, 31)), ]; }); diff --git a/database/migrations/2020_08_18_085018_create_businesses_table.php b/database/migrations/2020_08_18_085018_create_businesses_table.php index e37d713..b0d168f 100644 --- a/database/migrations/2020_08_18_085018_create_businesses_table.php +++ b/database/migrations/2020_08_18_085018_create_businesses_table.php @@ -23,7 +23,7 @@ class CreateBusinessesTable extends Migration $table->string('description')->nullable(); $table->json('cache')->nullable(); $table->boolean('has_avatar')->default(false); - $table->timestamp('calculated_at')->nullable(); + $table->timestamp('calculated_at')->nullable()->index(); $table->timestamp('created_at')->nullable(); $table->timestamp('updated_at')->nullable(); $table->timestamp('deleted_at')->nullable(); From c2f9518b9aa21516856ae7531a38bef840137483 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Sun, 7 Mar 2021 15:43:41 +0330 Subject: [PATCH 07/11] Bug fix in fingerprints --- database/migrations/2020_08_18_085017_fingerprints.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2020_08_18_085017_fingerprints.php b/database/migrations/2020_08_18_085017_fingerprints.php index 67bd1ee..af506ec 100644 --- a/database/migrations/2020_08_18_085017_fingerprints.php +++ b/database/migrations/2020_08_18_085017_fingerprints.php @@ -19,8 +19,8 @@ class Fingerprints extends Migration $table->string('agent'); $table->ipAddress('ip'); $table->string('os'); - $table->decimal('latitude', 10, 2); - $table->decimal('longitude', 11, 2); + $table->decimal('latitude', 10, 4); + $table->decimal('longitude', 11, 4); $table->char('token', 60)->unique(); $table->timestamps(); }); From c89ea8683854c83a493e4ecd34beea8f7c435b20 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Sun, 7 Mar 2021 17:24:41 +0330 Subject: [PATCH 08/11] Remove redunant packages --- composer.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/composer.json b/composer.json index ca1dcad..a8d0129 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,6 @@ "jenssegers/agent": "^2.6", "laravel/socialite": "^5.1", "laravel/framework": "^8.12", - "guzzlehttp/guzzle": "^7.0.1", - "laravel/legacy-factories": "^1", - "fruitcake/laravel-cors": "^2.0", - "laravel/lumen-framework": "^8.0", - "illuminate/notifications": "^8.0", "league/flysystem-aws-s3-v3": "~1.0", "spatie/laravel-medialibrary": "^9.0", "spatie/laravel-query-builder": "^3.3", From c9bf70b9dc29a1d4379b509d6501ce89b25d70fa Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Sun, 7 Mar 2021 17:34:29 +0330 Subject: [PATCH 09/11] Fix a few problem after removing lumen --- app/Http/Controllers/AuthController.php | 1 - app/Models/Model.php | 4 ---- app/Models/User.php | 4 ++-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 7e9a193..55fe6f7 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -13,7 +13,6 @@ use App\Http\Resources\UserResource; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Cache; -use Laravel\Lumen\Routing\Controller; use Laravel\Socialite\Facades\Socialite; use Illuminate\Session\TokenMismatchException; use Symfony\Component\HttpFoundation\Response; diff --git a/app/Models/Model.php b/app/Models/Model.php index c68422f..b2f1373 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -2,11 +2,7 @@ namespace App\Models; -use Anik\Amqp\Exchange; -use Anik\Amqp\Facades\Amqp; use App\Events\ModelSaved; -use PhpAmqpLib\Wire\AMQPTable; -use Anik\Amqp\PublishableMessage; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; diff --git a/app/Models/User.php b/app/Models/User.php index 857c7d3..72c3b64 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,13 +5,13 @@ namespace App\Models; use App\Models\File; use App\Models\Model; use App\Models\SoftDeletes; -use App\Models\ReportableRelation; use Illuminate\Validation\Rule; use Illuminate\Http\UploadedFile; use Spatie\MediaLibrary\HasMedia; +use App\Models\ReportableRelation; use Illuminate\Auth\Authenticatable; -use Laravel\Lumen\Auth\Authorizable; use Spatie\MediaLibrary\InteractsWithMedia; +use Illuminate\Foundation\Auth\Access\Authorizable; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; From 34998e384f9a2d996672cee56f911fcec83c3585 Mon Sep 17 00:00:00 2001 From: akbarjimi Date: Mon, 8 Mar 2021 16:34:57 +0330 Subject: [PATCH 10/11] Is there an award for this? Cost worker improvement --- app/Console/Commands/CostCommand.php | 104 +++++++++++------------- app/Http/Controllers/FileController.php | 6 +- app/Http/Resources/FileResource.php | 2 - app/Models/Business.php | 1 + app/Models/Cost.php | 4 + app/Providers/AuthServiceProvider.php | 2 +- composer.json | 2 + 7 files changed, 59 insertions(+), 62 deletions(-) diff --git a/app/Console/Commands/CostCommand.php b/app/Console/Commands/CostCommand.php index 9845050..1e05de4 100644 --- a/app/Console/Commands/CostCommand.php +++ b/app/Console/Commands/CostCommand.php @@ -4,78 +4,60 @@ namespace App\Console\Commands; use Throwable; use Carbon\Carbon; +use App\Models\Cost; use App\Models\Business; use Illuminate\Console\Command; use Morilog\Jalali\CalendarUtils; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Storage; class CostCommand extends Command { - public const USER_FEE = 400; - - public const FILE_FEE = [ - 200 => 0, - 400 => 1000, - 800 => 2000, - ]; - protected $signature = 'cost:work'; protected $description = 'Run the cost worker'; - public function __construct() - { - parent::__construct(); - } - public function handle() { while (true) { - $business = Business::find(221); - if ($business === null) { + $lock = Cache::get('lock', false); + if ($lock) { + $this->info('There is no business for auditing.'); continue; } - $year = jdate()->getYear(); - $month = jdate()->getMonth(); - $day = jdate()->getDay(); - $hour = jdate()->getHour(); - $minute = jdate()->getMinute(); - $second = jdate()->getSecond(); - - if (jdate()->getYear() > jdate($business->calculated_at)->getYear()) { - $year = jdate()->getYear(); - $month = 1; - $day = 1; + $business = Business::orderBy('calculated_at')->first(); + if ($business === null) { + continue; } - if ( - jdate()->getYear() > jdate($business->calculated_at)->getYear() - && - jdate()->getMonth() > jdate($business->calculated_at)->getMonth() - ) { - $year = jdate()->getYear(); - $month = jdate()->getMonth(); - $day = 1; + if ($business->calculated_at->isFuture()) { + continue; } - $gDate = CalendarUtils::toGregorian($year, $month, $day); - $carbon = Carbon::createFromDate($gDate[0], $gDate[1], $gDate[2]); - $carbon->setTime($hour, $minute, $second); + $next_month = jdate($business->calculated_at)->addMonths(); + [$year, $month, $day] = CalendarUtils::toGregorian( + $next_month->getYear(), $next_month->getMonth(), 1 + ); + $now = Carbon::createFromDate($year, $month, $day); + $now->setTime(0, 0, 0); - $now = $carbon; + if ($now->isFuture()) { + $now = Carbon::now(); + } // if calculated_at less than an hour stop - if ($now->diffInMinutes($business->calculated_at)) { + if ($now->diffInMinutes($business->calculated_at) <= 59) { $this->info('Must be one hour after the last audit.'); + Cache::put('lock', true, $now->diffInSeconds($business->calculated_at)); continue; } try { DB::beginTransaction(); // Fixed amounts of expenses - $business->load('users', 'cost'); + $business->load('users', 'files'); $costs = 0; $costs += $this->calculateCostOfBusinessUsers($business, $now); @@ -86,11 +68,13 @@ class CostCommand extends Command // make sure save the calculated_at $business->update([ 'wallet' => $business->wallet - $costs, - 'calculated_at' => Carbon::now(), + 'calculated_at' => $now, ]); DB::commit(); + $this->info("The business #{$business->id} was audited."); } catch (Throwable $throwable) { + throw $throwable; DB::rollback(); report($throwable); continue; @@ -98,16 +82,18 @@ class CostCommand extends Command } } - public function calculateCostOfBusinessUsers($business, $until_now) + public function calculateCostOfBusinessUsers($business, $now) { $user_fee = enum('business.fee.user'); - $calculated_at = $business->calculated_at; $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); + if ($business->users->isEmpty()) { + return 0; + } // get business employee $users_cost = $business->cost - ->where('type', '=', 'users') + ->where('type', '=', Cost::USER_TYPE) ->where('fee', '=', $user_fee) ->where('month', '=', $recorded_month) ->where('amount', '=', $business->users->count()) @@ -115,16 +101,16 @@ class CostCommand extends Command if ($users_cost === null) { $business->cost()->create([ - 'type' => 'users', + 'type' => Cost::USER_TYPE, 'month' => $recorded_month, 'amount' => $business->users->count(), 'fee' => $user_fee, - 'duration' => $duration = $until_now->diffInSeconds($calculated_at), // from the created_at time of the newset fifth user + 'duration' => $duration = $now->diffInMinutes($business->calculated_at), // from the created_at time of the newset fifth user 'additional' => $business->users->pluck('id')->toArray(), ]); } else { $users_cost->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at) + $users_cost->duration, // last calc - (current month - now else last calc - end of the past month), + 'duration' => $duration = $now->diffInMinutes($business->calculated_at) + $users_cost->duration, // last calc - (current month - now else last calc - end of the past month), 'additional' => $business->users->pluck('id')->toArray(), ]); } @@ -132,7 +118,7 @@ class CostCommand extends Command return $user_fee * $duration; } - public function calculateCostOfBusinessFiles($business, $until_now) + public function calculateCostOfBusinessFiles($business, $now) { $file_fee = enum('business.fee.file'); $calculated_at = $business->calculated_at; @@ -140,31 +126,33 @@ class CostCommand extends Command // do the math in php - if (intdiv($business->files_volume, 200) === 0) { - $pads = 0; + $packs = intdiv($business->files_volume, 200); + if ($packs === 0) { + return 0; } else { - $pads = intdiv($business->files_volume, 200) - 1; + $packs--; } - $files = $business->cost - ->where('type', '=', 'files') + ->where('type', '=', Cost::FILE_TYPE) ->where('fee', '=', $file_fee) ->where('month', '=', $recorded_month) - ->where('amount', '=', $business->files_volume) + ->where('amount', '=', $packs) ->first(); if ($files === null) { $business->cost()->create([ - 'type' => 'files', + 'type' => Cost::FILE_TYPE, 'month' => $recorded_month, - 'amount' => $pads, + 'amount' => $packs, 'fee' => $file_fee, - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // how to determine the file?, + 'duration' => $duration = $now->diffInMinutes($calculated_at), // how to determine the file?, + 'additional' => ['volume' => $business->files_volume], ]); } else { $files->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at) + $files->duration, // last calc - (current month - now else last calc - end of the past month),, + 'duration' => $duration = $now->diffInMinutes($calculated_at) + $files->duration, // last calc - (current month - now else last calc - end of the past month),, + 'additional' => ['volume' => $business->files_volume], ]); } diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index a132732..23b30ca 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -9,8 +9,8 @@ use App\Models\Business; use Illuminate\Support\Str; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; +use App\Http\Resources\FileResource; use Illuminate\Support\Facades\Auth; -use App\HiLib\Resources\FileResource; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Illuminate\Support\Facades\Storage; @@ -127,6 +127,10 @@ class FileController extends Controller 'description' => $request->description ]); + $business->update([ + 'files_volume' => $business->files_volume + $file_record->size + ]); + return new FileResource($file_record); } diff --git a/app/Http/Resources/FileResource.php b/app/Http/Resources/FileResource.php index 3b74fb0..bc99db1 100644 --- a/app/Http/Resources/FileResource.php +++ b/app/Http/Resources/FileResource.php @@ -1,9 +1,7 @@ 'boolean', + 'calculated_at' => 'datetime', ]; public function getValueOf(?string $key) diff --git a/app/Models/Cost.php b/app/Models/Cost.php index fa06239..98b2e63 100644 --- a/app/Models/Cost.php +++ b/app/Models/Cost.php @@ -6,6 +6,10 @@ use App\Models\Model; class Cost extends Model { + public const USER_TYPE = 'users'; + + public const FILE_TYPE = 'files'; + public $perPage = 12; protected $fillable = [ diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index c02d1b6..69ff803 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -40,7 +40,7 @@ class AuthServiceProvider extends ServiceProvider 'token' => $request->bearerToken(), 'agent' => $request->getAgent(), 'os' => $request->getOS(), - ])->first(); + ])->firstOrFail(); return $fingerprint->user->setAttribute('token', $fingerprint->token); }); diff --git a/composer.json b/composer.json index a8d0129..6c87a76 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,8 @@ "jenssegers/agent": "^2.6", "laravel/socialite": "^5.1", "laravel/framework": "^8.12", + "laravel/legacy-factories": "^1", + "fruitcake/laravel-cors": "^2.0", "league/flysystem-aws-s3-v3": "~1.0", "spatie/laravel-medialibrary": "^9.0", "spatie/laravel-query-builder": "^3.3", From 51d2edd0d56dde2e0a275db9434415140902875a Mon Sep 17 00:00:00 2001 From: mahdihty Date: Sun, 7 Mar 2021 19:42:00 +0330 Subject: [PATCH 11/11] add event listener, notification and fa validations some change in enums --- app/Enums/cruds.php | 11 ++ app/Enums/tables.php | 51 +++++-- app/Events/TagCreate.php | 38 +++++ app/Listeners/NotifHandler.php | 36 +++++ app/Listeners/TagCreateNotif.php | 37 +++++ app/Models/User.php | 4 +- app/Notifications/MailNotification.php | 87 ++++++++++++ app/Providers/EventServiceProvider.php | 9 +- config/mail.php | 4 +- docker-compose.yml | 9 ++ resources/lang/fa/auth.php | 19 +++ resources/lang/fa/notification.php | 20 +++ resources/lang/fa/pagination.php | 19 +++ resources/lang/fa/passwords.php | 22 +++ resources/lang/fa/validation.php | 189 +++++++++++++++++++++++++ 15 files changed, 539 insertions(+), 16 deletions(-) create mode 100644 app/Enums/cruds.php create mode 100644 app/Events/TagCreate.php create mode 100644 app/Listeners/NotifHandler.php create mode 100644 app/Listeners/TagCreateNotif.php create mode 100644 app/Notifications/MailNotification.php create mode 100644 resources/lang/fa/auth.php create mode 100644 resources/lang/fa/notification.php create mode 100644 resources/lang/fa/pagination.php create mode 100644 resources/lang/fa/passwords.php create mode 100644 resources/lang/fa/validation.php diff --git a/app/Enums/cruds.php b/app/Enums/cruds.php new file mode 100644 index 0000000..1980e2e --- /dev/null +++ b/app/Enums/cruds.php @@ -0,0 +1,11 @@ + [ + 10 => ['name' => 'Create'], + 20 => ['name' => 'Update'], + 30 => ['name' => 'Delete'], + ], + +]; diff --git a/app/Enums/tables.php b/app/Enums/tables.php index 95a3d03..273bf4c 100644 --- a/app/Enums/tables.php +++ b/app/Enums/tables.php @@ -2,55 +2,82 @@ return [ + 'inverse' => [ + 10 => ['name' => 'Businesses'], + 20 => ['name' => 'Projects'], + 30 => ['name' => 'Sprints'], + 40 => ['name' => 'Statuses'], + 50 => ['name' => 'Systems'], + 60 => ['name' => 'Workflows'], + 70 => ['name' => 'Tags'], + 80 => ['name' => 'Tasks'], + 90 => ['name' => 'Works'], + 210 => ['name' => 'BusinessUser'], + 220 => ['name' => 'ProjectUser'], + 230 => ['name' => 'TagTask'], + ], + //Main Table's 'businesses' => [ 'id' => 10, - 'name' => 'Businesses' + 'name' => 'Businesses', + 'singular_name' => 'Business', ], 'projects' => [ 'id' => 20, - 'name' => 'Projects' + 'name' => 'Projects', + 'singular_name' => 'Project', ], 'sprints' => [ 'id' => 30, - 'name' => 'Sprints' + 'name' => 'Sprints', + 'singular_name' => 'Sprint', ], 'statuses' => [ 'id' => 40, - 'name' => 'Statuses' + 'name' => 'Statuses', + 'singular_name' => 'Status', ], 'systems' => [ 'id' => 50, - 'name' => 'Systems' + 'name' => 'Systems', + 'singular_name' => 'System', ], 'workflows' => [ 'id' => 60, - 'name' => 'Workflows' + 'name' => 'Workflows', + 'singular_name' => 'Workflow', ], 'tags' => [ 'id' => 70, - 'name' => 'Tags' + 'name' => 'Tags', + 'singular_name' => 'Tag', ], 'tasks' => [ 'id' => 80, - 'name' => 'Tasks' + 'name' => 'Tasks', + 'singular_name' => 'Task', ], 'works' => [ 'id' => 90, - 'name' => 'Works' + 'name' => 'Works', + 'singular_name' => 'Work', ], //Relation Table's 'business_user' => [ 'id' => 210, - 'name' => 'BusinessUser' + 'name' => 'BusinessUser', + 'singular_name' => 'BusinessUser', ], 'project_user' => [ 'id' => 220, - 'name' => 'ProjectUser' + 'name' => 'ProjectUser', + 'singular_name' => 'ProjectUser', ], 'tag_task' => [ 'id' => 230, - 'name' => 'TagTask' + 'name' => 'TagTask', + 'singular_name' => 'TagTask', ], ]; diff --git a/app/Events/TagCreate.php b/app/Events/TagCreate.php new file mode 100644 index 0000000..0a86866 --- /dev/null +++ b/app/Events/TagCreate.php @@ -0,0 +1,38 @@ +message = $message; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Listeners/NotifHandler.php b/app/Listeners/NotifHandler.php new file mode 100644 index 0000000..1fed4db --- /dev/null +++ b/app/Listeners/NotifHandler.php @@ -0,0 +1,36 @@ +message); + $event_class = 'App\Events\\'.enum('tables.'.$message->data->table_name.'.singular_name').enum('cruds.inverse.'.$message->data->crud_id.'.name'); + if (class_exists($event_class)) { +// event(new ('App\Events\\'.$event_class($message))); + $event_class::dispatch($message); + } + } +} diff --git a/app/Listeners/TagCreateNotif.php b/app/Listeners/TagCreateNotif.php new file mode 100644 index 0000000..8222723 --- /dev/null +++ b/app/Listeners/TagCreateNotif.php @@ -0,0 +1,37 @@ +message; + $notif_line = 'tags.create'; + $users = User::where('id', '<', 10)->get(); + Notification::send($users, new MailNotification($notif_line)); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 72c3b64..53d2106 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,6 +10,7 @@ use Illuminate\Http\UploadedFile; use Spatie\MediaLibrary\HasMedia; use App\Models\ReportableRelation; use Illuminate\Auth\Authenticatable; +use Illuminate\Notifications\Notifiable; use Spatie\MediaLibrary\InteractsWithMedia; use Illuminate\Foundation\Auth\Access\Authorizable; use Spatie\MediaLibrary\MediaCollections\Models\Media; @@ -18,7 +19,8 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; class User extends Model implements AuthenticatableContract, AuthorizableContract, HasMedia { - use SoftDeletes, + use Notifiable, + SoftDeletes, Authorizable, Authenticatable, InteractsWithMedia; diff --git a/app/Notifications/MailNotification.php b/app/Notifications/MailNotification.php new file mode 100644 index 0000000..4122952 --- /dev/null +++ b/app/Notifications/MailNotification.php @@ -0,0 +1,87 @@ +message = $message; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->line(__('notification.'.$this->message)) + ->action('Notification Action', url('/')) + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 4d702ef..c627395 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -3,7 +3,10 @@ namespace App\Providers; use App\Events\ModelSaved; +use App\Events\TagCreate; use App\Listeners\ActivityRegistration; +use App\Listeners\NotifHandler; +use App\Listeners\TagCreateNotif; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -21,7 +24,11 @@ class EventServiceProvider extends ServiceProvider SendEmailVerificationNotification::class, ], ModelSaved::class => [ - ActivityRegistration::class + ActivityRegistration::class, + NotifHandler::class, + ], + TagCreate::class => [ + TagCreateNotif::class, ] ]; diff --git a/config/mail.php b/config/mail.php index 54299aa..022418f 100644 --- a/config/mail.php +++ b/config/mail.php @@ -36,8 +36,8 @@ return [ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), - 'port' => env('MAIL_PORT', 587), + 'host' => env('MAIL_HOST', 'localhost'), + 'port' => env('MAIL_PORT', 1025), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), diff --git a/docker-compose.yml b/docker-compose.yml index 96ea2ee..d606ce7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,6 +100,15 @@ services: - mysql networks: - sail + mailhog: + image: mailhog/mailhog + logging: + driver: 'none' # disable saving logs + ports: + - 1025:1025 # smtp server + - 8025:8025 # web ui + networks: + - sail networks: sail: driver: bridge diff --git a/resources/lang/fa/auth.php b/resources/lang/fa/auth.php new file mode 100644 index 0000000..a5831f3 --- /dev/null +++ b/resources/lang/fa/auth.php @@ -0,0 +1,19 @@ + 'اطلاعات وارد شده صحیح نمی باشد', + 'throttle' => 'درخواست بیش از حد مجاز! لطفا بعد از :seconds ثانیه دوباره امتحان کنید', + +]; diff --git a/resources/lang/fa/notification.php b/resources/lang/fa/notification.php new file mode 100644 index 0000000..e885bcc --- /dev/null +++ b/resources/lang/fa/notification.php @@ -0,0 +1,20 @@ + [ + 'create' => 'یک تگ ساخته شد.' + ] + +]; diff --git a/resources/lang/fa/pagination.php b/resources/lang/fa/pagination.php new file mode 100644 index 0000000..e00adbf --- /dev/null +++ b/resources/lang/fa/pagination.php @@ -0,0 +1,19 @@ + '« قبلی', + 'next' => 'بعدی »', + +]; diff --git a/resources/lang/fa/passwords.php b/resources/lang/fa/passwords.php new file mode 100644 index 0000000..671b232 --- /dev/null +++ b/resources/lang/fa/passwords.php @@ -0,0 +1,22 @@ + 'رمز عبور شما با موفقیت بازیابی شد', + 'sent' => 'ایمیلی برای بازیابی رمزعبور برای شما ارسال شد', + 'throttled' => 'لطفا اندکی صبر کنید', + 'token' => 'مشخصه بازیابی رمزعبور شما صحیح نمی باشد', + 'user' => "کاربری با این اطلاعات وجود ندارد", + +]; diff --git a/resources/lang/fa/validation.php b/resources/lang/fa/validation.php new file mode 100644 index 0000000..54b4e77 --- /dev/null +++ b/resources/lang/fa/validation.php @@ -0,0 +1,189 @@ + 'گزینه :attribute باید تایید شود', + 'active_url' => 'گزینه :attribute یک آدرس سایت معتبر نیست', + 'after' => 'گزینه :attribute باید تاریخی بعد از :date باشد', + 'after_or_equal' => 'گزینه :attribute باید تاریخی مساوی یا بعد از :date باشد', + 'alpha' => 'گزینه :attribute باید تنها شامل حروف باشد', + 'alpha_dash' => 'گزینه :attribute باید تنها شامل حروف، اعداد، خط تیره و زیر خط باشد', + 'alpha_num' => 'گزینه :attribute باید تنها شامل حروف و اعداد باشد', + 'array' => 'گزینه :attribute باید آرایه باشد', + 'before' => 'گزینه :attribute باید تاریخی قبل از :date باشد', + 'before_or_equal' => 'گزینه :attribute باید تاریخی مساوی یا قبل از :date باشد', + 'between' => [ + 'numeric' => 'گزینه :attribute باید بین :min و :max باشد', + 'file' => 'گزینه :attribute باید بین :min و :max کیلوبایت باشد', + 'string' => 'گزینه :attribute باید بین :min و :max کاراکتر باشد', + 'array' => 'گزینه :attribute باید بین :min و :max آیتم باشد', + ], + 'boolean' => 'گزینه :attribute تنها می تواند صحیح یا غلط باشد', + 'confirmed' => 'تایید مجدد گزینه :attribute صحیح نمی باشد', + 'date' => 'گزینه :attribute یک تاریخ صحیح نمی باشد', + 'date_equals' => 'گزینه :attribute باید تاریخی مساوی با :date باشد', + 'date_format' => 'گزینه :attribute با فرمت :format همخوانی ندارد', + 'different' => 'گزینه :attribute و :other باید متفاوت باشند', + 'digits' => 'گزینه :attribute باید :digits عدد باشد', + 'digits_between' => 'گزینه :attribute باید بین :min و :max عدد باشد', + 'dimensions' => 'ابعاد تصویر گزینه :attribute مجاز نمی باشد', + 'distinct' => 'گزینه :attribute دارای افزونگی داده می باشد', + 'email' => 'گزینه :attribute باید یک آدرس ایمیل صحیح باشد', + 'ends_with' => 'گزینه :attribute باید با یکی از این مقادیر پایان یابد، :values', + 'exists' => 'گزینه انتخاب شده :attribute صحیح نمی باشد', + 'file' => 'گزینه :attribute باید یک فایل باشد', + 'filled' => 'گزینه :attribute نمی تواند خالی باشد', + 'gt' => [ + 'numeric' => 'گزینه :attribute باید بزرگتر از :value باشد', + 'file' => 'گزینه :attribute باید بزرگتر از :value کیلوبایت باشد', + 'string' => 'گزینه :attribute باید بزرگتر از :value کاراکتر باشد', + 'array' => 'گزینه :attribute باید بیشتر از :value آیتم باشد', + ], + 'gte' => [ + 'numeric' => 'گزینه :attribute باید بزرگتر یا مساوی :value باشد', + 'file' => 'گزینه :attribute باید بزرگتر یا مساوی :value کیلوبایت باشد', + 'string' => 'گزینه :attribute باید بزرگتر یا مساوی :value کاراکتر باشد', + 'array' => 'گزینه :attribute باید :value آیتم یا بیشتر داشته باشد', + ], + 'image' => 'گزینه :attribute باید از نوع تصویر باشد', + 'in' => 'گزینه انتخابی :attribute صحیح نمی باشد', + 'in_array' => 'گزینه :attribute در :other وجود ندارد', + 'integer' => 'گزینه :attribute باید از نوع عددی باشد', + 'ip' => 'گزینه :attribute باید آی پی آدرس باشد', + 'ipv4' => 'گزینه :attribute باید آی پی آدرس ورژن 4 باشد', + 'ipv6' => 'گزینه :attribute باید آی پی آدرس ورژن 6 باشد', + 'json' => 'گزینه :attribute باید از نوع رشته جیسون باشد', + 'lt' => [ + 'numeric' => 'گزینه :attribute باید کمتر از :value باشد', + 'file' => 'گزینه :attribute باید کمتر از :value کیلوبایت باشد', + 'string' => 'گزینه :attribute باید کمتر از :value کاراکتر باشد', + 'array' => 'گزینه :attribute باید کمتر از :value آیتم داشته باشد', + ], + 'lte' => [ + 'numeric' => 'گزینه :attribute باید مساوی یا کمتر از :value باشد', + 'file' => 'گزینه :attribute باید مساوی یا کمتر از :value کیلوبایت باشد', + 'string' => 'گزینه :attribute باید مساوی یا کمتر از :value کاراکتر باشد', + 'array' => 'گزینه :attribute نباید کمتر از :value آیتم داشته باشد', + ], + 'max' => [ + 'numeric' => 'گزینه :attribute نباید بزرگتر از :max باشد', + 'file' => 'گزینه :attribute نباید بزرگتر از :max کیلوبایت باشد', + 'string' => 'گزینه :attribute نباید بزرگتر از :max کاراکتر باشد', + 'array' => 'گزینه :attribute نباید بیشتر از :max آیتم داشته باشد', + ], + 'mimes' => 'گزینه :attribute باید دارای یکی از این فرمت ها باشد: :values', + 'mimetypes' => 'گزینه :attribute باید دارای یکی از این فرمت ها باشد: :values', + 'min' => [ + 'numeric' => 'گزینه :attribute باید حداقل :min باشد', + 'file' => 'گزینه :attribute باید حداقل :min کیلوبایت باشد', + 'string' => 'گزینه :attribute باید حداقل :min کاراکتر باشد', + 'array' => 'گزینه :attribute باید حداقل :min آیتم داشته باشد', + ], + 'not_in' => 'گزینه انتخابی :attribute صحیح نمی باشد', + 'not_regex' => 'فرمت گزینه :attribute صحیح نمی باشد', + 'numeric' => 'گزینه :attribute باید از نوع عددی باشد', + 'password' => 'رمزعبور صحیح نمی باشد', + 'present' => 'گزینه :attribute باید از نوع درصد باشد', + 'regex' => 'فرمت گزینه :attribute صحیح نمی باشد', + 'required' => 'تکمیل گزینه :attribute الزامی است', + 'required_if' => 'تکمیل گزینه :attribute زمانی که :other دارای مقدار :value است الزامی می باشد', + 'required_unless' => 'تکمیل گزینه :attribute الزامی می باشد مگر :other دارای مقدار :values باشد', + 'required_with' => 'تکمیل گزینه :attribute زمانی که مقدار :values درصد است الزامی است', + 'required_with_all' => 'تکمیل گزینه :attribute زمانی که مقادیر :values درصد است الزامی می باشد', + 'required_without' => 'تکمیل گزینه :attribute زمانی که مقدار :values درصد نیست الزامی است', + 'required_without_all' => 'تکمیل گزینه :attribute زمانی که هیچ کدام از مقادیر :values درصد نیست الزامی است', + 'same' => 'گزینه های :attribute و :other باید یکی باشند', + 'size' => [ + 'numeric' => 'گزینه :attribute باید :size باشد', + 'file' => 'گزینه :attribute باید :size کیلوبایت باشد', + 'string' => 'گزینه :attribute باید :size کاراکتر باشد', + 'array' => 'گزینه :attribute باید شامل :size آیتم باشد', + ], + 'starts_with' => 'گزینه :attribute باید با یکی از این مقادیر شروع شود، :values', + 'string' => 'گزینه :attribute باید تنها شامل حروف باشد', + 'timezone' => 'گزینه :attribute باید از نوع منطقه زمانی صحیح باشد', + 'unique' => 'این :attribute از قبل ثبت شده است', + 'uploaded' => 'آپلود گزینه :attribute شکست خورد', + 'url' => 'فرمت :attribute اشتباه است', + 'uuid' => 'گزینه :attribute باید یک UUID صحیح باشد', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'name' => 'نام', + 'username' => 'نام کاربری', + 'email' => 'ایمیل', + 'first_name' => 'نام', + 'last_name' => 'نام خانوادگی', + 'password' => 'رمز عبور', + 'password_confirmation' => 'تاییدیه رمز عبور', + 'city' => 'شهر', + 'state' => 'استان', + 'country' => 'کشور', + 'address' => 'آدرس', + 'phone' => 'تلفن', + 'mobile' => 'تلفن همراه', + 'age' => 'سن', + 'sex' => 'جنسیت', + 'gender' => 'جنسیت', + 'day' => 'روز', + 'month' => 'ماه', + 'year' => 'سال', + 'hour' => 'ساعت', + 'minute' => 'دقیقه', + 'second' => 'ثانیه', + 'title' => 'عنوان', + 'text' => 'متن', + 'content' => 'محتوا', + 'description' => 'توضیحات', + 'date' => 'تاریخ', + 'time' => 'زمان', + 'available' => 'موجود', + 'type' => 'نوع', + 'img' => 'تصویر', + 'image' => 'تصویر', + 'size' => 'اندازه', + 'color' => 'رنگ', + 'captcha' => 'کد امنیتی', + 'price' => 'قیمت', + 'pic' => 'تصویر', + 'link' => 'لینک', + ], +];