diff --git a/app/Channels/FcmChannel.php b/app/Channels/FcmChannel.php new file mode 100644 index 0000000..e141bb4 --- /dev/null +++ b/app/Channels/FcmChannel.php @@ -0,0 +1,23 @@ +toFcm($notifiable); + + $recipients = $notifiable->routeNotificationFor('fcm', $notification); + } +} diff --git a/app/Channels/Messages/FcmMessage.php b/app/Channels/Messages/FcmMessage.php new file mode 100644 index 0000000..dc81a8a --- /dev/null +++ b/app/Channels/Messages/FcmMessage.php @@ -0,0 +1,132 @@ +to = $to[0]; + } else { + $this->to = $to; + } + + return $this; + } + + /** + * Set the topic of the FCM message. + * + * @param string $topic + * @return $this + */ + public function topic(string $topic) + { + $this->topic = $topic; + + return $this; + } + + /** + * Set the data of the FCM message. + * + * @param array $data + * @return $this + */ + public function data(array $data) + { + $this->data = $data; + + return $this; + } + + /** + * Set the notification of the FCM message. + * + * @param array $notification + * @return $this + */ + public function notification(array $notification) + { + $this->notification = $notification; + + return $this; + } + + /** + * Set the condition for receive the FCM message. + * + * @param string $condition + * @return $this + */ + public function condition(string $condition) + { + $this->condition = $condition; + + return $this; + } + + /** + * Set the priority of the FCM message. + * + * @param string $priority + * @return $this + */ + public function priority(string $priority) + { + $this->priority = $priority; + + return $this; + } +} 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/Enums/cruds.php b/app/Enums/cruds.php new file mode 100644 index 0000000..283ea3b --- /dev/null +++ b/app/Enums/cruds.php @@ -0,0 +1,11 @@ + [ + 10 => ['name' => 'Create', 'singular_name' => 'create'], + 20 => ['name' => 'Update', 'singular_name' => 'update'], + 30 => ['name' => 'Delete', 'singular_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/BusinessUserCreate.php b/app/Events/BusinessUserCreate.php new file mode 100644 index 0000000..b150a8d --- /dev/null +++ b/app/Events/BusinessUserCreate.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/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/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 @@ message; + $new_user = User::findOrFail($payload->data->original->user_id); + $owners = Business::findOrFail($payload->business)->owners()->where('id', '!=', $new_user->id)->get(); + + $notif = [ + 'body' => __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name'), ['business' => request('_business_info')['name'], 'user' => $new_user->name]) + ]; + + $users = $owners->prepend($new_user); + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + } +} 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/Business.php b/app/Models/Business.php index eaab90f..dc03aef 100644 --- a/app/Models/Business.php +++ b/app/Models/Business.php @@ -42,6 +42,7 @@ class Business extends Model implements HasMedia protected $casts = [ 'has_avatar' => '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/Models/ReportableRelation.php b/app/Models/ReportableRelation.php index b8b5d07..f525bf0 100644 --- a/app/Models/ReportableRelation.php +++ b/app/Models/ReportableRelation.php @@ -4,6 +4,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\Support\Facades\Auth; @@ -56,21 +57,22 @@ class ReportableRelation extends Pivot 'from' => env('CONTAINER_NAME'), ]; - $message = new PublishableMessage(json_encode($payload)); - - $routers = [ - "activity_exchange" => ["name" => "activity",], - "notif_exchange" => ["name" => "notif",], - "socket_exchange" => ["name" => "socket",], - ]; - - foreach ($routers as $exchange => $properties) { - $message->setProperties(["application_headers" => new AMQPTable($properties)]); - - $message->setExchange(new Exchange($exchange)); - - Amqp::publish($message, ""); - } + ModelSaved::dispatch(json_encode($payload)); +// $message = new PublishableMessage(json_encode($payload)); +// +// $routers = [ +// "activity_exchange" => ["name" => "activity",], +// "notif_exchange" => ["name" => "notif",], +// "socket_exchange" => ["name" => "socket",], +// ]; +// +// foreach ($routers as $exchange => $properties) { +// $message->setProperties(["application_headers" => new AMQPTable($properties)]); +// +// $message->setExchange(new Exchange($exchange)); +// +// Amqp::publish($message, ""); +// } } public function properties() diff --git a/app/Models/User.php b/app/Models/User.php index 72c3b64..52767e4 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,8 +8,8 @@ use App\Models\SoftDeletes; use Illuminate\Validation\Rule; 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 +18,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/DBNotification.php b/app/Notifications/DBNotification.php new file mode 100644 index 0000000..9c76954 --- /dev/null +++ b/app/Notifications/DBNotification.php @@ -0,0 +1,63 @@ +message = $message; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['database']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->line('The introduction to the notification.') + ->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 [ + 'data' => $this->message['body'] + ]; + } +} diff --git a/app/Notifications/FcmNotification.php b/app/Notifications/FcmNotification.php new file mode 100644 index 0000000..c302a64 --- /dev/null +++ b/app/Notifications/FcmNotification.php @@ -0,0 +1,48 @@ +to([]) + } + +} diff --git a/app/Notifications/MailNotification.php b/app/Notifications/MailNotification.php new file mode 100644 index 0000000..abe937b --- /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($this->message['body']) + ->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/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ee8ca5b..7b917de 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use App\Channels\FcmChannel; +use Illuminate\Notifications\ChannelManager; +use Illuminate\Support\Facades\Notification; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -13,7 +16,11 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + Notification::resolved(function (ChannelManager $service) { + $service->extend('fcm', function ($app) { + return new FcmChannel(new HttpClient, config('fcm.key')); + }); + }); } /** 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/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 4d702ef..c3a0983 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,8 +2,13 @@ namespace App\Providers; +use App\Events\BusinessUserCreate; use App\Events\ModelSaved; +use App\Events\TagCreate; use App\Listeners\ActivityRegistration; +use App\Listeners\BusinessUserCreateNotif; +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,8 +26,15 @@ class EventServiceProvider extends ServiceProvider SendEmailVerificationNotification::class, ], ModelSaved::class => [ - ActivityRegistration::class - ] + ActivityRegistration::class, + NotifHandler::class, + ], + TagCreate::class => [ + TagCreateNotif::class, + ], + BusinessUserCreate::class => [ + BusinessUserCreateNotif::class, + ], ]; /** 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", 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/database/migrations/2021_03_08_114700_create_notifications_table.php b/database/migrations/2021_03_08_114700_create_notifications_table.php new file mode 100644 index 0000000..9797596 --- /dev/null +++ b/database/migrations/2021_03_08_114700_create_notifications_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('notifications'); + } +} 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..d6e95b4 --- /dev/null +++ b/resources/lang/fa/notification.php @@ -0,0 +1,24 @@ + [ + 'create' => 'یک تگ ساخته شد.' + ], + + 'business_user' => [ + 'create' => 'کاربر :user به کسب و کار :business اضافه شد.' + ] + +]; 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' => 'لینک', + ], +];