diff --git a/.env.example b/.env.example index c3ed2a9..fde9c84 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,11 @@ -APP_NAME=Laravel +CONTAINER_NAME= + +APP_NAME=Liwo APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost +APP_TIMEZONE="Asia/Tehran" LOG_CHANNEL=stack LOG_LEVEL=debug @@ -22,8 +25,9 @@ SESSION_LIFETIME=120 MEMCACHED_HOST=127.0.0.1 -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null +REDIS_HOST=redis +REDIS_PASSWORD=root +REDIS_USER=root REDIS_PORT=6379 MAIL_MAILER=smtp @@ -47,3 +51,4 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + diff --git a/.gitignore b/.gitignore index 0ae59f0..0796583 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ Homestead.json Homestead.yaml npm-debug.log yarn-error.log +.idea/ + diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/liwo.iml b/.idea/liwo.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/liwo.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index f1e8f43..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/Channels/FcmChannel.php b/app/Channels/FcmChannel.php new file mode 100644 index 0000000..382bfcf --- /dev/null +++ b/app/Channels/FcmChannel.php @@ -0,0 +1,93 @@ +http = $http; + $this->apiKey = $apiKey; + } + + /** + * Send the given notification. + * + * @param mixed $notifiable + * @param \Illuminate\Notifications\Notification $notification + * @return void + */ + public function send($notifiable, Notification $notification) + { + $message = $notification->toFcm($notifiable); + + $message->to($notifiable->routeNotificationFor('fcm', $notification)); + + if (! $this->apiKey || (! $message->topic && ! $message->to)) { + return; + } + + $this->http->post(self::API_URI, [ + 'headers' => [ + 'Authorization' => "key={$this->apiKey}", + 'Content-Type' => 'application/json', + ], + 'json' => $this->buildJsonPayload($message), + ]); + } + + protected function buildJsonPayload(FcmMessage $message) + { + $payload = array_filter([ + 'priority' => $message->priority, + 'data' => $message->data, + 'notification' => $message->notification, + 'condition' => $message->condition, + ]); + + if ($message->topic) { + $payload['to'] = "/topics/{$message->topic}"; + } else { + if (is_array($message->to)) { + $payload['registration_ids'] = $message->to; + } else { + $payload['to'] = $message->to; + } + } + + return $payload; + } +} 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 new file mode 100644 index 0000000..949442b --- /dev/null +++ b/app/Console/Commands/CostCommand.php @@ -0,0 +1,169 @@ +info('There is no business for auditing.'); + continue; + } + + $business = Business::orderBy('calculated_at')->first(); + if ($business === null) { + continue; + } + + if ($business->calculated_at->isFuture()) { + continue; + } + + $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); + + if ($now->isFuture()) { + $now = Carbon::now(); + } + + // if calculated_at less than an hour stop + if ($now->diffInMinutes($business->calculated_at) <= 59) { + $this->info('Must be one hour after the last audit.'); + $this->ensureLockIsWritten(); + continue; + } + + try { + DB::beginTransaction(); + // Fixed amounts of expenses + $business->load('users', 'files'); + + $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' => $now, + ]); + + DB::commit(); + $this->info("The business #{$business->id} was audited."); + } catch (Throwable $throwable) { + throw $throwable; + DB::rollback(); + report($throwable); + continue; + } + } + } + + public function calculateCostOfBusinessUsers($business, $now) + { + $user_fee = enum('business.fee.user'); + $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', '=', Cost::USER_TYPE) + ->where('fee', '=', $user_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $business->users->count()) + ->first(); + + if ($users_cost === null) { + $business->cost()->create([ + 'type' => Cost::USER_TYPE, + 'month' => $recorded_month, + 'amount' => $business->users->count(), + 'fee' => $user_fee, + '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 = $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(), + ]); + } + + return $user_fee * $duration; + } + + public function calculateCostOfBusinessFiles($business, $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 + $packs = intdiv($business->files_volume, 200); + if ($packs === 0) { + return 0; + } else { + $packs--; + } + + $files = $business->cost + ->where('type', '=', Cost::FILE_TYPE) + ->where('fee', '=', $file_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $packs) + ->first(); + + if ($files === null) { + $business->cost()->create([ + 'type' => Cost::FILE_TYPE, + 'month' => $recorded_month, + 'amount' => $packs, + 'fee' => $file_fee, + 'duration' => $duration = $now->diffInMinutes($calculated_at), // how to determine the file?, + 'additional' => ['volume' => $business->files_volume], + ]); + } else { + $files->update([ + '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], + ]); + } + + return $file_fee * $duration; + } + + public function ensureLockIsWritten(): void + { + Cache::put('lock', true, 3600); + while (Cache::get('lock', false) === false) { + // prevent + } + } +} diff --git a/app/Enums/business.php b/app/Enums/business.php new file mode 100644 index 0000000..5690874 --- /dev/null +++ b/app/Enums/business.php @@ -0,0 +1 @@ + [ 'user' => 100, 'file' => 200, ] ]; \ No newline at end of file diff --git a/app/Enums/comment.php b/app/Enums/comment.php new file mode 100644 index 0000000..e516e01 --- /dev/null +++ b/app/Enums/comment.php @@ -0,0 +1 @@ + [ 'reject' => [ 'id' => 10, 'label' => 'رد' ], 'approve' => [ 'id' => 20, 'label' => 'تایید' ], ] ]; \ No newline at end of file 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/levels.php b/app/Enums/levels.php new file mode 100644 index 0000000..8d1b627 --- /dev/null +++ b/app/Enums/levels.php @@ -0,0 +1 @@ + [ 'id' => 4, 'name' => 'Owner', 'label' => 'صاحب' ], 'admin' => [ 'id' => 3, 'name' => 'Admin', 'label' => 'مدیر' ], 'colleague' => [ 'id' => 2, 'name' => 'Colleague', 'label' => 'همکار' ], 'guest' => [ 'id' => 1, 'name' => 'Guest', 'label' => 'مهمان' ], 'inactive' => [ 'id' => 0, 'name' => 'Inactive', 'label' => 'غیر فعال' ], ]; \ No newline at end of file diff --git a/app/Enums/log.php b/app/Enums/log.php new file mode 100644 index 0000000..3157502 --- /dev/null +++ b/app/Enums/log.php @@ -0,0 +1,25 @@ + [ + User::class => 10, + Business::class => 20, + Project::class => 30, + Task::class => 40, + Spenthour::class => 50, + ], + + 'actions' => [ + 'created' => 10, + 'updated' => 20, + 'deleted' => 30, + 'restored' => 40, + ], +]; diff --git a/app/Enums/post.php b/app/Enums/post.php new file mode 100644 index 0000000..0e8190c --- /dev/null +++ b/app/Enums/post.php @@ -0,0 +1 @@ + [ 'draft' => [ 'id' => 10, 'label' => 'پیش نویس' ], 'review' => [ 'id' => 20, 'label' => 'در حال بررسی' ], 'published' => [ 'id' => 30, 'label' => 'منتشر' ], 'trashed' => [ 'id' => 40, 'label' => 'حذف' ], ] ]; \ No newline at end of file diff --git a/app/Enums/roles.php b/app/Enums/roles.php new file mode 100644 index 0000000..6dd5eda --- /dev/null +++ b/app/Enums/roles.php @@ -0,0 +1 @@ + [ 'id' => 0, 'label' => 'غیر فعال' ], 'guest' => [ 'id' => 1, 'label' => 'میهمان' ], 'Colleague' => [ 'id' => 2, 'label' => 'همکار' ], 'senior' => [ 'id' => 3, 'label' => 'معاون' ], 'manager' => [ 'id' => 4, 'label' => 'مدیر' ], ]; \ No newline at end of file diff --git a/app/Enums/service.php b/app/Enums/service.php new file mode 100644 index 0000000..39ab78f --- /dev/null +++ b/app/Enums/service.php @@ -0,0 +1 @@ + [ 'file' => [ 'orphanage' => [ 'id' => 'orphanage', 'label' => 'داده‌های موقت', ] ] ] ]; \ No newline at end of file diff --git a/app/Enums/status.php b/app/Enums/status.php new file mode 100644 index 0000000..8bd99c4 --- /dev/null +++ b/app/Enums/status.php @@ -0,0 +1 @@ + [ 'inactive' => [ 'id' => 0, 'label' => 'غیر فعال', 'name' => 'Inactive' ], 'active' => [ 'id' => 1, 'label' => 'فعال', 'name' => 'Active' ], 'close' => [ 'id' => 2, 'label' => 'بسته', 'name' => 'Close' ], 'done' => [ 'id' => 3, 'label' => 'انجام شده', 'name' => 'Done' ], ], ]; \ No newline at end of file diff --git a/app/Enums/tables.php b/app/Enums/tables.php new file mode 100644 index 0000000..273bf4c --- /dev/null +++ b/app/Enums/tables.php @@ -0,0 +1,83 @@ + [ + 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', + 'singular_name' => 'Business', + ], + 'projects' => [ + 'id' => 20, + 'name' => 'Projects', + 'singular_name' => 'Project', + ], + 'sprints' => [ + 'id' => 30, + 'name' => 'Sprints', + 'singular_name' => 'Sprint', + ], + 'statuses' => [ + 'id' => 40, + 'name' => 'Statuses', + 'singular_name' => 'Status', + ], + 'systems' => [ + 'id' => 50, + 'name' => 'Systems', + 'singular_name' => 'System', + ], + 'workflows' => [ + 'id' => 60, + 'name' => 'Workflows', + 'singular_name' => 'Workflow', + ], + 'tags' => [ + 'id' => 70, + 'name' => 'Tags', + 'singular_name' => 'Tag', + ], + 'tasks' => [ + 'id' => 80, + 'name' => 'Tasks', + 'singular_name' => 'Task', + ], + 'works' => [ + 'id' => 90, + 'name' => 'Works', + 'singular_name' => 'Work', + ], + + //Relation Table's + 'business_user' => [ + 'id' => 210, + 'name' => 'BusinessUser', + 'singular_name' => 'BusinessUser', + ], + 'project_user' => [ + 'id' => 220, + 'name' => 'ProjectUser', + 'singular_name' => 'ProjectUser', + ], + 'tag_task' => [ + 'id' => 230, + 'name' => 'TagTask', + 'singular_name' => 'TagTask', + ], +]; diff --git a/app/Enums/ticket.php b/app/Enums/ticket.php new file mode 100644 index 0000000..5899fbf --- /dev/null +++ b/app/Enums/ticket.php @@ -0,0 +1 @@ + [ 'sale' => [ 'id' => 1, 'label' => 'فروش', ], 'support' => [ 'id' => 2, 'label' => 'پشتیبانی' ] ], 'category' => [ 'webdesign-sale' => [ 'id' => 1, 'label' => 'طراحی سایت', 'type' => 1 ], 'seo-sale' => [ 'id' => 2, 'label' => 'فروش سئو', 'type' => 1 ], 'webdesign-support' => [ 'id' => 3, 'label' => 'پشتیبانی طراحی سایت', 'type' => 2 ] ], 'status' => [ 'active' => [ 'id' => 1, 'label' => 'فعال' ], 'close' => [ 'id' => 2, 'label' => 'بسته' ] ] ]; \ No newline at end of file diff --git a/app/Enums/user.php b/app/Enums/user.php new file mode 100644 index 0000000..e639578 --- /dev/null +++ b/app/Enums/user.php @@ -0,0 +1 @@ + [ 'guest' => [ 'id' => 1, 'label' => 'مهمان' ], 'user' => [ 'id' => 2, 'label' => 'کاربر' ], 'service' => [ 'id' => 3, 'label' => 'سرویس' ] ], 'status' => [ 'desable' => [ 'id' => 0, 'label' => 'غیر فعال' ], 'active' => [ 'id' => 1, 'label' => 'فعال' ] ], 'permissions' => [ // user 'user-user-create', 'user-user-view-any', 'user-user-update-own', 'user-user-update-any', 'user-user-role-own', 'user-user-role-any', 'user-role-create', 'user-role-view-any', // ticket 'ticket-ticket-create', 'ticket-ticket-view-any', 'ticket-ticket-reply-any', // post 'post-post-create', 'post-post-view-publish', 'post-post-view-any', 'post-post-view-own', 'post-post-update-own', 'post-post-update-any', 'post-post-delete-own', 'post-post-delete-any', // tag 'post-tag-update-any', 'post-tag-create', 'post-tag-delete-any', // taxonomies 'post-taxonomy-view-any', 'post-taxonomy-update-any', 'post-taxonomy-create', 'post-taxonomy-delete-any', // comments 'post-comment-view-any', 'post-comment-view-published', 'post-comment-view-own', 'post-comment-create', 'post-comment-update-any', 'post-comment-delete-any', ] ]; \ No newline at end of file diff --git a/app/Events/BusinessUpdate.php b/app/Events/BusinessUpdate.php new file mode 100644 index 0000000..f2ffa55 --- /dev/null +++ b/app/Events/BusinessUpdate.php @@ -0,0 +1,20 @@ +message = $message; + } +} 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/ModelSaved.php b/app/Events/ModelSaved.php new file mode 100644 index 0000000..32a28cc --- /dev/null +++ b/app/Events/ModelSaved.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/ProjectUserCreate.php b/app/Events/ProjectUserCreate.php new file mode 100644 index 0000000..fa5e385 --- /dev/null +++ b/app/Events/ProjectUserCreate.php @@ -0,0 +1,24 @@ +message = $message; + } +} 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/Events/TaskCreate.php b/app/Events/TaskCreate.php new file mode 100644 index 0000000..e51dff2 --- /dev/null +++ b/app/Events/TaskCreate.php @@ -0,0 +1,28 @@ +message = $message; + } +} diff --git a/app/Events/TaskUpdate.php b/app/Events/TaskUpdate.php new file mode 100644 index 0000000..91e18bd --- /dev/null +++ b/app/Events/TaskUpdate.php @@ -0,0 +1,28 @@ +message = $message; + } +} diff --git a/app/Http/Controllers/ActivityController.php b/app/Http/Controllers/ActivityController.php new file mode 100644 index 0000000..870279c --- /dev/null +++ b/app/Http/Controllers/ActivityController.php @@ -0,0 +1,110 @@ +indexValidation($request); + $per_page = $request->limit > 100 ? 10 : $request->limit; + return $this->indexFiltering($business)->paginate($per_page); + } + + public function indexValidation($request) + { + $bound = 10; + $this->validate($request, [ + 'filter.project_id' => [new MaxBound($bound)] , + 'filter.system_id' => [new MaxBound($bound)] , + 'filter.workflow_id' => [new MaxBound($bound)] , + 'filter.status_id' => [new MaxBound($bound)] , + 'filter.sprint_id' => [new MaxBound($bound)] , + 'filter.actor_id' => [new MaxBound($bound)] , + 'filter.user_id' => [new MaxBound($bound)] , + 'filter.subject_id' => [new MaxBound($bound)] , + //todo: validation for crud_id and table_id + 'filter.creates_before' => 'bail|nullable|date|date_format:Y-m-d' , + 'filter.creates_after' => 'bail|nullable|date|date_format:Y-m-d' , + 'filter.creates_in' => 'bail|nullable|numeric|max:90' , + ]); + } + public function indexFiltering($business) + { + $query = Activity::where('business_id', $business); + $activityQ = QueryBuilder::for($query) + ->allowedFilters([ + AllowedFilter::exact('project_id'), + AllowedFilter::exact('system_id'), + AllowedFilter::exact('workflow_id'), + AllowedFilter::exact('status_id'), + AllowedFilter::exact('sprint_id'), + AllowedFilter::exact('task_id'), + AllowedFilter::exact('actor_id'), + AllowedFilter::exact('user_id'), + AllowedFilter::exact('crud_id'), + AllowedFilter::exact('table_id'), + AllowedFilter::exact('subject_id'), + AllowedFilter::scope('creates_before'), + AllowedFilter::scope('creates_after'), + AllowedFilter::scope('creates_in'), + ]) + ->defaultSort('-id') + ->allowedSorts('id', 'created_at'); + if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) { + $requested_projects = isset(\request('filter')['project_id']) ? + array_unique(explode(',',\request('filter')['project_id'] ?? null )) : + null; + $requested_projects = collect($requested_projects)->keyBy(null)->toArray(); + $project_ids = $this->myStateProjects($requested_projects); + $activityQ->where(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['non_guest_ids']) + ->orWhere(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['guest_ids']) + ->where('user_id', auth()->id()); + }); + }); + } + return $activityQ; + } + + public function myStateProjects($requested_projects) + { + $non_guest_ids = []; + $guest_ids = []; + $is_empty = empty($requested_projects); + + foreach (\request('_business_info')['info']['projects'] as $p_id => $p) { + + $level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level']; + + if (( $is_empty || isset($requested_projects[$p_id])) + && $level > enum('levels.guest.id')) { + array_push($non_guest_ids, $p_id); + } + if (( $is_empty || isset($requested_projects[$p_id])) + && $level == enum('levels.guest.id')) { + array_push($guest_ids, $p_id); + } + } + + return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids]; + } + + public function store($business, Request $request) + { + return Activity::create($request->merge(['business_id' => $business])->all()); + } + + public function delete() + { + + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php new file mode 100644 index 0000000..55fe6f7 --- /dev/null +++ b/app/Http/Controllers/AuthController.php @@ -0,0 +1,268 @@ +stateless()->redirect(); + } + + public function handleGoogleCallback(Request $request) + { + try { + + $user = Socialite::driver('google')->stateless()->user(); + $find_user = User::where('email', $user->email)->first(); + + if ($find_user) { + + $find_user->update([ + 'active' => true + ]); + + Auth::setUser($find_user); + + } else { + + $user = User::create($user->user + [ + 'password' => Hash::make('google-login-user'), + 'username' => $user->email, + 'active' => true + ]); + + Auth::setUser($user); + + } + $finger_print = $this->createFingerPrint(); + return redirect('http://localhost:3000/login?token='.$finger_print->token); + + } catch (Exception $e) { + dd($e->getMessage()); + } + } + + public function login(Request $request) + { + // todo: Logging in from a new device will result in sending a notification + $this->validate($request, [ + 'email' => 'required|email|exists:users,email', + 'password' => 'required|string|min:6' + ]); + + $user = User::where('email', $request->email)->first(); + if ($user && Hash::check($request->password, $user->password)) { + Auth::setUser($user); + + return [ + 'auth' => $this->createFingerPrint(), + 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid)) + ]; + } + + return new JsonResponse([ + 'message' => trans('auth.failed'), + 'status' => Response::HTTP_NOT_FOUND, + ], Response::HTTP_NOT_FOUND); + } + + + public function register(Request $request) + { + $this->validate($request, [ + 'name' => 'required|string|max:225|min:2', + 'username' => ['required', Rule::unique('users', 'username')], + 'email' => ['required', 'email', Rule::unique('users', 'email')], + 'password' => 'required|string|min:8' + ]); + + $request->merge(['password' => Hash::make($request->password)]); + + $code_data = ['verification_code' => $this->sendVerificationCode()]; + $method_data = ['method' => 'registerMain']; + + Cache::put($request->email, $request->all() + $code_data + $method_data, 3600); // remain one hour + + return \response()->json([ + 'message' => 'Code send for user and user must be verified.'], + Response::HTTP_OK); + + } + + public function registerMain($user_info) + { + $user = User::create($user_info); + + Auth::setUser($user); + + return $this->createFingerPrint(); + } + + public function sendVerificationCode($contact_way = null) + { + $verification_code = 1234; // rand(10001, 99999) + + //send code for user with contact way + + return $verification_code; + } + + public function verification(Request $request) + { + if (!Cache::has($request->email)) { + return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST); + } + + $user_info = Cache::get($request->email); + + $this->validate($request, [ + 'email' => 'required|email', + 'verification_code' => 'required|string|min:4|max:4|in:'.$user_info['verification_code'] + ]); + + Cache::forget($request->email); + + return isset($user_info['method']) ? + call_user_func('self::'.$user_info['method'], $user_info) : + \response()->json(['message' => 'Code verified successfully.'], Response::HTTP_OK,); + } + + public function forgetPassword(Request $request) + { + $this->validate($request, [ + 'email' => 'required|email|exists:users,email' + ]); + + $code_data = ['verification_code' => $this->sendVerificationCode()]; + + Cache::put($request->email, $request->all() + $code_data, 3600); // remain one hour + + return \response()->json([ + 'message' => 'Code send for user and user must be verified.'], + Response::HTTP_OK); + } + + public function updatePassword(Request $request) + { + if (!Cache::has($request->email)) { + return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST); + } + + $this->validate($request, [ + 'email' => 'required|email', + 'password' => 'required|string|min:8|confirmed', + 'verification_code' => 'required|string|min:4|max:4|in:'.Cache::get($request->email)['verification_code'] + ]); + + $user = User::where('email', $request->email)->first(); + + $user->update([ + 'password' => Hash::make($request->password) + ]); + + Auth::setUser($user); + + return $this->createFingerPrint(); + } + + /** + * @param Request $request + * @return mixed + * @throws TokenMismatchException + */ + public function logout(Request $request) + { + $token = $request->bearerToken(); + + if (blank($token)) { + return new JsonResponse([ + 'message' => 'Not authorized request.', + 'status' => Response::HTTP_UNAUTHORIZED + ]); + } + + /** @var Fingerprint $token */ + $token = Auth::user()->fingerprints()->firstWhere([ + 'token' => $token, + ]); + + if ($token) { + return $token->delete(); + } + + throw new TokenMismatchException('Invalid token!'); + } + + /** + * @param string $token + * @throws TokenMismatchException + */ + public function revoke(string $token) + { + /** @var Fingerprint $token */ + $token = Fingerprint::firstWhere([ + 'token' => $token, + ]); + + if ($token) { + return $token->delete(); + } + + throw new TokenMismatchException(); + } + + public function auth() + { + return new UserResource(Auth::user()); + } + + public function authWithInfo() + { + return [ + 'auth' => new UserResource(Auth::user()), + 'businesses' => Auth::user()->businesses->keyBy('id') ->map(fn($b, $bid) => Business::info($bid)) + ]; + } + + public function delete(Request $request) + { + Auth::user()->fingerprints()->delete(); + unset(Auth::user()->token); + Auth::user()->delete(); + + return 'success'; + } + + public function createFingerPrint() + { + $attributes = [ + 'agent' => request()->getAgent(), + 'ip' => request()->getClientIp(), + 'os' => request()->getOS(), + 'latitude' => \request()->getLocation()->getAttribute('lat'), + 'longitude' => \request()->getLocation()->getAttribute('lon'), + ]; + + $values = [ + 'token' => Str::random(60) + ]; + + return Auth::user()->fingerprints()->firstOrCreate($attributes, $attributes + $values); + } +} diff --git a/app/Http/Controllers/BusinessController.php b/app/Http/Controllers/BusinessController.php new file mode 100644 index 0000000..aafdd41 --- /dev/null +++ b/app/Http/Controllers/BusinessController.php @@ -0,0 +1,195 @@ +user()->businesses + ->keyBy('id') + ->map(fn($b, $bid) => Business::info($bid)); + } + + public function store(Request $request) + { +// $users = []; +// foreach ($request->users ?? [] as $key => $value) { +// $users[$value] = []; +// } + +// $owner = [ +// Auth::id() => [ +// 'level' => enum('levels.owner.id'), +// ] +// ]; +// +// $users = $users + $owner; +// +// $request->merge(['users' => $users]); + + $business = Business::create($request->all()); + $business->users()->sync([Auth::id() => [ + 'level' => enum('levels.owner.id'), + ] + ], false); + + return Business::info($business->id); + } + + public function show(string $business) + { + permit('businessAccess'); + return Business::info($business); + } + + public function update(Request $request, string $business) + { + // permit('businessEdit'); + $business = Business::findOrFail($business); + $business->fill($request->all())->save(); + + return Business::info($business->id); + } + + public function setAvatar(Request $request, string $business) + { + $business = Business::findOrFail($business); + if ($request->hasFile('avatar')) { + $business->saveAsAvatar($request->file('avatar')); + } + + return Business::info($business->id); + } + + public function unSetAvatar(Request $request, string $business) + { + $business = Business::findOrFail($business); + $business->deleteAvatar(); + + return Business::info($business->id); + } + + + public function info(string $business) + { + return request('_business_info'); + } + + public function restore(string $business) + { + $business = Business::onlyTrashed()->findOrFail($business); + $business->restore(); + + return response(['message' => 'business successfully restored.']); + } + + public function storeOrUpdateUser($business, Request $request) + { + permit('businessUsers'); + $validatedData = $this->validate($request, [ + 'level' => 'required|numeric|between:0,4', + 'user_id' => 'required|numeric|not_in:'.auth()->id(), + ]); + + DB::transaction(function () use ($validatedData, $request, $business) { + $this->addUser($business, $request->user_id, $validatedData); + if (can('businessAccess', ['user_id'=> $request->user_id])) { + //update + $this->relatedUpdateChanges($request->user_id, $request->level); + } + }, 3); + + return Business::info($business, true); + } + + public function relatedUpdateChanges($user_id, $level) + { + if ($level == enum('levels.owner.id')) { + // user up level to owner + $this->removeProjectDirectRelation($user_id); + } + if ($level != enum('levels.owner.id') && + $level > request('_business_info')['info']['users'][$user_id]['level']) { + // user at least up level to $request->level + $this->updateProjectAccessLevel($level, $user_id); + } + } + + public function addUser($business, $user, $validatedData) + { + $businessModel = Business::findOrFail($business); + $businessModel->users()->sync([$user => $validatedData], false); + } + + public function removeProjectDirectRelation($user) + { + $userModel = User::findOrFail($user); + return $userModel->projects()->sync([], true); + } + + public function updateProjectAccessLevel($level, $user) + { + $ids = []; + foreach (request('_business_info')['projects'] as $project_id => $item) { + foreach ($item['members'] as $idx => $member) { + if ($member['id'] == $user && $member['level'] != enum('levels.inactive.id') && $member['level'] < $level) { + $ids[$project_id] = ['level' => $level]; + break; + } + } + } + $userModel = User::findOrFail($user); + return $userModel->projects()->sync($ids, false); + } + + public function deleteUser($business, $user) + { + permit('businessAccess'); + $this->checkDeleteUserPolicy($user); + $businessModel = Business::findOrFail($business); + + DB::transaction(function () use ($user, $businessModel) { + $this->detachUser($businessModel, $user); + $this->removeProjectDirectRelation($user); + }, 3); + + return Business::info($business, true); + } + + public function haveAnotherOwner($user) + { + foreach (request('_business_info')['info']['users'] as $id => $item) { + if ($item['level'] == enum('levels.owner.id') && $id != $user) { + return true; + } + } + return false; + } + + public function detachUser($business, $user) + { + return $business->users()->sync( + $business->users->except($user)->pluck('id')->toArray() + ); + } + + public function checkDeleteUserPolicy($user) + { + if (!can('isBusinessOwner') && auth()->id() != $user ) { + // Non owner user remove another owner + abort(405); + } + if (can('isBusinessOwner') && auth()->id() == $user && !$this->haveAnotherOwner($user)) { + // Owner remove self but business haven't another owner + abort(405); + } + } + +} diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php new file mode 100644 index 0000000..898d232 --- /dev/null +++ b/app/Http/Controllers/CommentController.php @@ -0,0 +1,90 @@ + $project]); + $taskModel = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail(); + if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest) + return $taskModel->assignee_id == \auth()->id() ? + Comment::where([ + ['business_id', $business], + ['project_id', $project], + ['task_id', $task], + ])->get(): + abort(Response::HTTP_FORBIDDEN); // not allowed + } else { + return Comment::where([ + ['business_id', $business], + ['project_id', $project], + ['task_id', $task], + ])->get(); + } + } + + public function store($business, $project, $task, Request $request) + { + permit('projectAccess', ['project_id' => $project]); + $taskModel = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail(); + if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest) + return $taskModel->assignee_id == \auth()->id() ? + Comment::create($request->merge([ + 'business_id' => $business, + 'project_id' => $project, + 'task_id' => $task, + 'user_id' => \auth()->id(), + ])->except('_business_info')) : + abort(Response::HTTP_FORBIDDEN); // not allowed + } else { + return Comment::create($request->merge([ + 'business_id' => $business, + 'project_id' => $project, + 'task_id' => $task, + 'user_id' => \auth()->id(), + ])->except('_business_info')); + } + } + + public function show($business, $project, $task, $comment) + { + permit('projectAccess', ['project_id' => $project]); + $taskModel = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail(); + if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest) + return $taskModel->assignee_id == \auth()->id() ? + Comment::findOrFail($comment) : + abort(Response::HTTP_FORBIDDEN); // not allowed + } else { + return Comment::findOrFail($comment); + } + } + + public function update($business, $project, $task, $comment, Request $request) + { + permit('projectAccess', ['project_id' => $project]); + $comment = Comment::findOrFail($comment); + if ($comment->user_id == \auth()->id()) { + $comment->update($request->except('_business_info')); + return $comment; + } + return abort(Response::HTTP_FORBIDDEN); // not allowed + } + + public function destroy($business, $project, $task, $comment) + { + permit('projectAccess', ['project_id' => $project]); + $comment = Comment::findOrFail($comment); + if ($comment->user_id == \auth()->id()) { + $comment->delete(); + return \response()->json(['message' => 'comment deleted successfully.'], Response::HTTP_OK); + } + return abort(Response::HTTP_FORBIDDEN); // not allowed + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a0a2a8a..03e02a2 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers; -use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; -use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Foundation\Validation\ValidatesRequests; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class Controller extends BaseController { diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php new file mode 100644 index 0000000..d1f311b --- /dev/null +++ b/app/Http/Controllers/CreditController.php @@ -0,0 +1,69 @@ +allowedSorts([ + 'amount', + 'succeeded', + 'created_at', + ]) + ->allowedFilters([ + AllowedFilter::exact('user_id'), + AllowedFilter::exact('succeeded'), + ]); + + return TransactionResource::collection( + $builder->paginate($request->per_page) + ); + } + + public function pay(Request $request, int $business) + { + \permit('isBusinessOwner'); + + return Transaction::create([ + 'user_id'=> Auth::id(), + 'business_id'=> $business, + 'amount'=> $request->amount, + ]); + } + + public function redirection($transaction) + { + $transaction = Transaction::findOrFail($transaction); + if ($transaction->isWentToPaymentGateway()) { + throw new \Exception("Siktir baba ye bar ghablan rafti."); + } + + return $transaction->prepare()->redirect(); + } + + public function callback(Request $request) + { + $transaction = Transaction::findByAuthority($request->get('Authority'))->verify(); + if (!$transaction->hasBeenAppliedToWallet() && $transaction->succeeded) { + $transaction->business->increment("wallet", $transaction->amount); + $transaction->amountWasAppliedToWallet(); + + return true; + } + + throw new \Exception("تراکنش تایید نشد"); + } +} diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php new file mode 100644 index 0000000..23b30ca --- /dev/null +++ b/app/Http/Controllers/FileController.php @@ -0,0 +1,207 @@ +indexValidation($request); + + $per_page = $request->limit > 100 ? 10 : $request->limit; + + return $this->indexFiltering($business)->paginate($per_page); + } + + public function indexValidation($request) + { + $bound = 10; + $this->validate($request, [ + 'filter.project_id' => [new MaxBound($bound)] , + 'filter.user_id' => [new MaxBound($bound)] , + ]); + } + + public function indexFiltering($business) + { + $query = File::where('business_id', $business); + $fileQ = QueryBuilder::for($query) + ->allowedFilters([ + AllowedFilter::exact('user_id'), + AllowedFilter::exact('project_id'), + AllowedFilter::exact('extension'), + ]); + if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) { + $requested_projects = isset(\request('filter')['project_id']) ? + array_unique(explode(',',\request('filter')['project_id'] ?? null )) : + null; + $requested_projects = collect($requested_projects)->keyBy(null)->toArray(); + $project_ids = $this->myStateProjects($requested_projects); + $fileQ->where(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['non_guest_ids']) + ->orWhere(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['guest_ids']) + ->where('user_id', auth()->id()); + }); + }); + } + + if (request()->filled('group')) { + $fileQ->selectRaw("files.group, count(files.id) as file_count, sum(files.size) as file_size")->groupBy('group'); + } + + return $fileQ; + } + + public function myStateProjects($requested_projects) + { + $non_guest_ids = []; + $guest_ids = []; + $is_empty = empty($requested_projects); + + foreach (\request('_business_info')['info']['projects'] as $p_id => $p) { + + $level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level']; + + if (( $is_empty || isset($requested_projects[$p_id])) + && $level > enum('levels.guest.id')) { + array_push($non_guest_ids, $p_id); + } + if (( $is_empty || isset($requested_projects[$p_id])) + && $level == enum('levels.guest.id')) { + array_push($guest_ids, $p_id); + } + } + + return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids]; + } + + public function store(Request $request, int $business, int $project) + { + // different size and different validation + // validate + // validate the wallet is not so much in debt + // create record in the db + // put file in s3 + // return file resource + $business = Business::findOrFail($business); + $project = Project::findOrFail($project); + + $this->validate($request, ['file' => 'required|file',]); + + $file = $request->file('file'); + $file_extension = $file->getClientOriginalExtension(); + $file_name = Str::random(40).'.'.$file_extension; + $file->storeAs( + $business->id.\DIRECTORY_SEPARATOR.$project->id, + $file_name, + 's3' + ); + + $file_record = File::create([ + 'user_id' => Auth::id(), + 'business_id' => $business->id, + 'project_id' => $project->id, + 'disk' => 's3', // default disk + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file_extension, + 'name' => $file_name, + 'mime' => $file->getClientMimeType(), + 'group' => $this->groupDetection($file), + 'size' => $file->getSize(), + 'description' => $request->description + ]); + + $business->update([ + 'files_volume' => $business->files_volume + $file_record->size + ]); + + return new FileResource($file_record); + } + + public function groupDetection(UploadedFile $file) + { + // Media files like mp4, mp3, wma and png or jpeg + [$type, $subtype] = Str::of($file->getMimeType())->explode("/",2)->pad(2, null); + + if (in_array($type, ['audio', 'video', 'image'])) { + return $type; + } + + // Covert string to \Illuminate\Support\Stringable object + $subtype = Str::of($subtype); + + // PDF files + if ($subtype->contains(["pdf"])) { + return "pdf"; + } + + // Compressed files like zip, cab, rar, etc. + if ($subtype->contains(['compressed']) || in_array($file->getClientOriginalExtension(), ['zip', 'rar','7z','cab'])) { + return "compressed"; + } + + // Office files like xls, xlsx, doc, docx, etc. + if ($subtype->contains(['vnd.ms', 'vnd.sealed', 'officedocument', 'opendocument'])) { + return "office"; + } + + // Non of the above files + return "other"; + } + + public function download(int $business, int $project, int $file) + { + // requested file belongs to this project and this business + // check permisson + // create perma link or temp link + // return the file resource or stream it + return File::findOrFail($file)->getTemporaryLink(); + } + + public function rename(Request $request, int $business, int $project, int $file) + { + // requested file belongs to this project and this business + // check permisson + // update original name + // return the file resource + // sanitize the name for slashs and back slashes + $this->validate($request, [ + 'name' => 'required|string' + ]); + + $file = File::findOrFail($file); + + $file->update(['original_name' => $request->name.".".$file->extension]); + + return new FileResource($file); + } + + + public function delete(Request $request, int $business, int $project, int $file) + { + // requested file belongs to this project and this business + // check permisson + // check it's relations + // delete the file form File table + // delete file from s3 + $file = File::findOrFail($file); + Storage::disk('s3')->delete($file->getPath()); + return $file->delete(); + } +} diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php new file mode 100644 index 0000000..b6ee5ac --- /dev/null +++ b/app/Http/Controllers/InvoiceController.php @@ -0,0 +1,80 @@ +selectRaw("concat_ws('-',business_id,month) as factor_id") + ->selectRaw("MIN(created_at) as begin") + ->selectRaw("MAX(updated_at) as end") + ->selectRaw("sum(cost) as cost") + ->selectRaw("sum(tax) as tax") + ->where('business_id','=',$business->id) + ->groupBy('month','factor_id'); + + $costs = QueryBuilder::for($builder) + ->allowedSorts([ + 'factor_id', + 'begin', + 'end', + 'cost', + ]) + ->allowedFilters([ + AllowedFilter::exact('month'), + AllowedFilter::exact('type'), + ]); + + return $costs->paginate($request->per_page); + } + + public function indexFiltering($business) + { + $query = File::where('business_id', $business); + $fileQ = QueryBuilder::for($query) + ->allowedFilters([ + AllowedFilter::exact('user_id'), + AllowedFilter::exact('project_id'), + AllowedFilter::exact('extension'), + ]); + if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) { + $requested_projects = isset(\request('filter')['project_id']) ? + array_unique(explode(',', \request('filter')['project_id'] ?? null)) : + null; + $requested_projects = collect($requested_projects)->keyBy(null)->toArray(); + $project_ids = $this->myStateProjects($requested_projects); + $fileQ->where(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['non_guest_ids']) + ->orWhere(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['guest_ids']) + ->where('user_id', auth()->id()); + }); + }); + } + + if (request()->filled('group')) { + $fileQ->selectRaw("files.group, count(files.id) as file_count, sum(files.size) as file_size")->groupBy('group'); + } + + return $fileQ; + } + + public function show(Request $request, int $business, string $date) + { + return Cost::where('business_id', '=', $business) + ->where("month","=",$date) + ->get(); + } +} diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php new file mode 100644 index 0000000..00a20cb --- /dev/null +++ b/app/Http/Controllers/ProjectController.php @@ -0,0 +1,159 @@ +get(); + } + + public function store(Request $request, string $business) + { + permit('businessProjects'); + Project::create($request->merge(['business_id' => $business])->all()); + return Business::info($request->route('business'), true); + } + + public function update(Request $request, int $business, string $project) + { + permit('projectEdit', ['project_id' => $project]); + $project = Project::findOrFail($project); + $project->update($request->except('business_id')); + return Business::info($request->route('business'), true); + } + + + public function delete(Request $request, int $business, string $project) + { + permit('businessProjects'); + $project = Project::findOrFail($project); + $project->delete(); + return Business::info($request->route('business')); + } + + public function restore(Request $request, int $business, string $project) + { + $project = Project::onlyTrashed()->findOrFail($project); + $project->restore(); + + return response(['message' => 'project successfully restored.']); + } + + public function storeOrUpdateUser($business, $project, Request $request) + { + permit('projectUsers', ['project_id' => $project]); + $validatedData = $this->validate($request, [ + 'level' => 'required|numeric|between:1,3', + 'user_id' => 'required|numeric|not_in:'.auth()->id(), + ]); + + $this->checkAddUserPolicy($request->user_id, $request->level); + + $projectModel = Project::findOrFail($project); + DB::transaction(function () use ($business, $validatedData, $request, $projectModel) { + $projectModel->members()->sync([$request->user_id => $validatedData], false); + + if (!can('businessAccess', ['user_id' => $request->user_id])) { + + // Register user to business with zero level + //User not exist in the business before + + $this->addUserWithZeroLevel($request->user_id, $business); + } + }, 3); + + return Business::info($projectModel->business_id, true); + } + + public function checkAddUserPolicy($user, $level) + { + if (can('businessAccess', ['user_id' => $user]) + && $level < request('_business_info')['info']['users'][$user]['level']) {// before in business + abort(405); + } + } + + public function addUserWithZeroLevel($user_id, $business) + { + $businessModel = Business::findOrFail($business); + return $businessModel->users()->sync([$user_id => [ + 'level' => 0, + 'user_id' => $user_id + ]], false); + } + + public function deleteUser($business, $project, $user) + { + permit('projectAccess', ['project_id' => $project]); + $this->checkDeleteUserPolicy($user, $project); + + $projectModel = Project::findOrFail($project); + + DB::transaction(function () use ($project, $business, $user, $projectModel) { + $this->detachMember($projectModel, $user); + if (!can('isActiveUser', ['user_id' => $user]) && !$this->haveOneProject($user, $project)) { + + // User level in business is zero + // And haven't another project then remove it form business + + $businessModel = Business::findOrFail($business); + $this->detachUser($businessModel, $user); + } + }, 3); + + return Business::info($projectModel->business_id, true); + } + + public function detachMember($project, $user) + { + return $project->members()->detach($user) ? true : abort(404); + } + + public function detachUser($business, $user) + { + return $business->users()->detach($user) ? true : abort(404); + } + + public function haveOneProject($user, $project) + { + foreach (request('_business_info')['info']['projects'] as $id => $item) { + if ($item['members'][$user]['level'] > enum('levels.inactive.id') && $id != $project) { + return true; + } + } + return false; + } + + public function checkDeleteUserPolicy($user, $project) + { + if (!can('isProjectOwner', ['project_id' => $project]) && (auth()->id() != $user) ) { + abort(405); + } + } + + public function setAvatar(Request $request, int $business, string $project) + { + $project = Project::findOrFail($project); + if ($request->hasFile('avatar')) { + $project->saveAsAvatar($request->file('avatar')); + } + + return $project; + } + + public function unSetAvatar(Request $request,int $business ,string $project) + { + $project = Project::findOrFail($project); + $project->deleteAvatar(); + + return $project; + } +} diff --git a/app/Http/Controllers/SprintController.php b/app/Http/Controllers/SprintController.php new file mode 100644 index 0000000..69d560c --- /dev/null +++ b/app/Http/Controllers/SprintController.php @@ -0,0 +1,45 @@ + $project]); + + Sprint::create($request->merge( + ['business_id' => $business, 'project_id' => $project] + )->except('_business_info')); + + return Business::info($business, true); + } + + public function update($business, $project, $sprint, Request $request) + { + permit('projectSprints', ['project_id' => $project]); + + $sprint = Sprint::findOrFail($sprint); + + $sprint->update($request->except('_business_info')); + + return Business::info($business, true); + } + + public function delete($business, $project, $sprint) + { + permit('projectSprints', ['project_id' => $project]); + + $sprint = Sprint::findOrFail($sprint); + + $sprint->delete(); + + return Business::info($business, true); + } + +} diff --git a/app/Http/Controllers/StatisticController.php b/app/Http/Controllers/StatisticController.php new file mode 100644 index 0000000..ae65111 --- /dev/null +++ b/app/Http/Controllers/StatisticController.php @@ -0,0 +1,112 @@ + 0, // The sum of all tasks that are under review + 'total' => 0, // Sum of all tasks + 'overdue' => 0, // The sum of all overworked tasks + 'spent_time' => 0, // The sum of all the minutes spent on tasks + 'estimated_time' => 0, // The sum of all the minutes spent performing tasks is estimated + 'over_spent_time' => 0, // The sum of all the minutes spent overworking tasks + 'under_spent_time' => 0, // The sum of all the minutes left until estimated time + ]; + + public function index(Request $request, int $business, ?int $project = null) + { + $builder = DB::table('tasks')->where('business_id','=',$business); + if ($project) { + $builder->where('project_id', '=', $project); + } + $tasks = $builder->get(); + + $tags = DB::table('tag_task') + ->whereIn('task_id', $tasks->pluck('id')->toArray()) + ->get() + ->groupBy('task_id') + ->toArray(); + + + $result = []; + + foreach ($tasks as $task) { + + $this->addProjects($result, $task); + + $this->addSprints($result, $task); + + $this->addSystems($result, $task); + + $this->addTags($result, $task, $tags[$task->id] ?? []); + } + + return $result; + } + + + public function addProjects(&$result, $task) + { + $key = "projects.{$task->project_id}"; + + $this->subset($key, $result, $task); + } + + public function addSprints(&$result, $task) + { + $key = "projects.{$task->project_id}."; + $key .= 'sprints.'; + $key .= ($task->sprint_id ?? 0) . '.'; + $key .= ($task->assignee_id ?? 0) . '.'; + $key .= $task->workflow_id . '.'; + $key .= $task->status_id; + + $this->subset($key, $result, $task); + } + + public function addSystems(&$result, $task) + { + $key = "projects.{$task->project_id}."; + $key .= 'systems.'; + $key .= ($task->system_id ?? 0) . '.'; + $key .= ($task->assignee_id ?? 0) . '.'; + $key .= $task->workflow_id . '.'; + $key .= $task->status_id; + + $this->subset($key, $result, $task); + } + + public function addTags(&$result, $task, $tags) + { + foreach ($tags as $tag) { + $key = "projects.{$task->project_id}."; + $key .= 'tags.'; + $key .= $tag->id . '.'; + $key .= ($task->assignee_id ?? 0) . '.'; + $key .= $task->workflow_id . '.'; + $key .= $task->status_id; + + $this->subset($key, $result, $task); + } + } + + public function subset($key , &$result, $task) + { + $node = Arr::get($result, $key, $this->map); + + $node['test'] += $task->ready_to_test; + $node['total'] += 1; + $node['overdue'] += !$task->on_time; + $node['spent_time'] += $task->spent_time; + $node['estimated_time'] += $task->estimated_time; + $node['over_spent_time'] += $task->estimated_time < $task->spent_time ? $task->spent_time - $task->estimated_time : 0; + $node['under_spent_time'] += $task->estimated_time > $task->spent_time ? $task->estimated_time - $task->spent_time : 0; + + Arr::set($result, $key, $node); + } +} diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php new file mode 100644 index 0000000..73cb572 --- /dev/null +++ b/app/Http/Controllers/StatusController.php @@ -0,0 +1,35 @@ +merge(['business_id' => $business, 'workflow_id' => $workflow])->except('_business_info')); + return Business::info($business, true); + } + + public function update($business, $workflow, $status, Request $request) + { + permit('businessStatuses'); + $status = Status::findOrFail($status); + $status->update($request->except('_business_info')); + return Business::info($business, true); + } + + public function delete($business, $workflow, $status) + { + permit('businessStatuses'); + $status = Status::findOrFail($status); + $status->delete(); + return Business::info($business, true); + } +} diff --git a/app/Http/Controllers/SystemController.php b/app/Http/Controllers/SystemController.php new file mode 100644 index 0000000..bb41f82 --- /dev/null +++ b/app/Http/Controllers/SystemController.php @@ -0,0 +1,40 @@ + $project]); + System::create([ + 'business_id' => $business, + 'project_id' => $project, + 'name' => $request->name + ]); + return Business::info($business, true); + } + + public function update($business, $project, $system, Request $request) + { + permit('projectSystems', ['project_id' => $project]); + $system = System::findOrFail($system); + $system->update($request->except('_business_info')); + return Business::info($business, true); + } + + + public function delete($business, $project, $system) + { + permit('projectSystems', ['project_id' => $project]); + $system = System::findOrFail($system); + $system->delete(); + return Business::info($business, true); + } + + +} diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php new file mode 100644 index 0000000..2667a2b --- /dev/null +++ b/app/Http/Controllers/TagController.php @@ -0,0 +1,34 @@ +merge(['business_id' => $business])->except('_business_info')); + return Business::info($business, true); + } + + public function update($business, $tag, Request $request) + { + permit('businessTags'); + $tag = Tag::findOrFail($tag); + $tag->update($request->except('_business_info')); + return Business::info($business, true); + } + + public function delete($business, $tag) + { + permit('businessTags'); + $tag = Tag::findOrFail($tag); + $tag->delete(); + return Business::info($business, true); + } + +} diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php new file mode 100644 index 0000000..02ef09b --- /dev/null +++ b/app/Http/Controllers/TaskController.php @@ -0,0 +1,295 @@ +limit > 100 ? 10 : $request->limit; + $this->indexValidation($request); + $tasks = $this->indexFiltering($business) + ->when($request->filled('group'), function ($q) use ($request) { + return $q->report($request->group); + }); + + return $request->filled('group') ? + $tasks->get()->groupBy($request->group) + ->map(function ($q) { return $q->keyBy('status_id'); }) + : new TaskCollection($tasks->paginate($per_page)); + } + + public function indexValidation($request) + { + $bound = 10; + $this->validate($request, [ + 'filter.project_id' => [new MaxBound($bound)] , + 'filter.creator_id' => [new MaxBound($bound)] , + 'filter.assignee_id' => [new MaxBound($bound)] , + 'filter.system_id' => [new MaxBound($bound)] , + 'filter.workflow_id' => [new MaxBound($bound)] , + 'filter.status_id' => [new MaxBound($bound)] , + 'filter.approver_id' => [new MaxBound($bound)] , + 'filter.priority_min' => 'nullable|numeric|between:1,10' , + 'filter.priority_max' => 'nullable|numeric|between:1,10' , + 'filter.ready_to_test' => 'nullable|boolean' , + 'filter.on_time' => 'nullable|boolean' , + ]); + } + + public function indexFiltering($business) + { + $query = Task::where('business_id', $business); + $taskQ = QueryBuilder::for($query) +// ->with('tags') + ->select(DB::raw('tasks.* , (spent_time - estimated_time) as over_spent')) + ->allowedFilters([ + AllowedFilter::exact('project_id'), + AllowedFilter::exact('system_id'), + AllowedFilter::exact('creator_id'), + AllowedFilter::exact('assignee_id'), + AllowedFilter::exact('approver_id'), + AllowedFilter::exact('sprint_id'), + AllowedFilter::exact('workflow_id'), + AllowedFilter::exact('status_id'), + AllowedFilter::exact('on_time'), + AllowedFilter::exact('ready_to_test'), + AllowedFilter::exact('tagTask.tag_id'), + AllowedFilter::scope('priority_min'), + AllowedFilter::scope('priority_max'), + AllowedFilter::scope('creates_before'), + AllowedFilter::scope('creates_after'), + AllowedFilter::scope('creates_in'), + AllowedFilter::scope('updates_before'), + AllowedFilter::scope('updates_after'), + AllowedFilter::scope('updates_in'), + AllowedFilter::scope('spent_from'), + AllowedFilter::scope('spent_to'), + AllowedFilter::scope('estimated_from'), + AllowedFilter::scope('estimated_to'), + AllowedFilter::scope('starts_before'), + AllowedFilter::scope('starts_after'), + AllowedFilter::scope('starts_in'), + AllowedFilter::scope('finish_before'), + AllowedFilter::scope('finish_after'), + AllowedFilter::scope('finish_in'), + AllowedFilter::scope('completes_before'), + AllowedFilter::scope('completes_after'), + AllowedFilter::scope('completes_in'), + AllowedFilter::scope('over_spent_from'), + AllowedFilter::scope('over_spent_to'), + AllowedFilter::scope('due_date_before'), + AllowedFilter::scope('due_date_after'), + AllowedFilter::scope('due_date_in'), + AllowedFilter::scope('my_watching'), + AllowedFilter::scope('over_due'), + ]); + if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) { + $requested_projects = isset(\request('filter')['project_id']) ? + array_unique(explode(',',\request('filter')['project_id'] ?? null )) : + null; + $requested_projects = collect($requested_projects)->keyBy(null)->toArray(); + $project_ids = $this->myStateProjects($requested_projects); + $taskQ->where(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['non_guest_ids']) + ->orWhere(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['guest_ids']) + ->where('assignee_id', auth()->id()); + }); + }); + } + return $taskQ; + } + + public function myStateProjects($requested_projects) + { + $non_guest_ids = []; + $guest_ids = []; + $is_empty = empty($requested_projects); + + foreach (\request('_business_info')['info']['projects'] as $p_id => $p) { + + $level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level']; + + if (( $is_empty || isset($requested_projects[$p_id])) + && $level > enum('levels.guest.id')) { + array_push($non_guest_ids, $p_id); + } + if (( $is_empty || isset($requested_projects[$p_id])) + && $level == enum('levels.guest.id')) { + array_push($guest_ids, $p_id); + } + } + + return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids]; + } + + public function store($business, $project, Request $request) + { + permit('projectTasks', ['project_id' => $project]); + + $task = Task::create($request->merge( + ['business_id' => $business, 'project_id' => $project, 'creator_id' => \auth()->id()] + )->except(['_business_info', 'completed_at', 'on_time', 'ready_to_test'])); + + + return new TaskResource($task); + } + + public function storeTagTasks($tags, $task) { + $tagModels = []; + if (isset($tags)) { + foreach ($tags as $tag) { + array_push($tagModels, + new TagTask(['tag_id' => $tag, 'task_id' => $task->id]) + ); + } + $task->tags()->saveMany($tagModels); + } + } + + /** + * Rule's: + * 1) guest's only can see self task + * 2) user is active in project + */ + public function show($business, $project, $task) + { + $task = Task::findOrFail($task); + $project = $task->project_id; + permit('projectAccess', ['project_id' => $project]); + if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest) + return $task->assignee_id == \auth()->id() ? + new TaskResource($task) : + abort(Response::HTTP_METHOD_NOT_ALLOWED); // not allowed + } else { + return new TaskResource($task); + } + } + + /** + * Rule's: + * 1) update assignee_id when not exist work time and active user + * 2) update approver_id when atLeast colleague + * 3) update ready_to_test when assignee_id == auth xor assignee_id == null and isAdmin + * 4) update tags + * 5) update completed_at when status in [done, close] + * 6) due_date before sprint end_time + */ + public function update($business, $project, $task, Request $request) + { + permit('isProjectGuest', ['project_id' => $project]); + $taskModel = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail(); + + if ($taskModel->assignee_id != \auth()->id() && can('isDefiniteGuestInProject', ['project_id' => $project])) { + permit('isDefiniteGuestInProject', ['project_id' => $project]); + } + + if($request->filled('watchers')) { + $watchers = $taskModel->watchers ?? []; + if(!can('isDefiniteGuestInProject', ['project_id' => $project]) && !can('projectTasks', ['project_id' => $project])) { + if (array_search(auth()->id(), $watchers) !== false) { + // remove auth from watchers + $watchers = array_values(\array_diff($watchers, [auth()->id()])); + } else { + // add auth to watchers + $watchers = array_merge($watchers, [auth()->id()]); + } + } + if(can('projectTasks', ['project_id' => $project])) { + $watchers = array_intersect($watchers ?? [], $request->watchers); + } + $request->merge(['watchers' => $watchers]); + } + + if (!can('projectTasks', ['project_id' => $project])) { + $request->replace($request->only(['_business_info', 'ready_to_test', 'status_id', 'watchers'])); + } + if ($taskModel->assignee_id != \auth()->id()) { + $request->request->remove('ready_to_test'); + } + + + if (isset(\request('_business_info')['workflows'][$request->workflow_id]['statuses'][$request->status_id]['state'])) { + $state = \request('_business_info')['workflows'][$request->workflow_id]['statuses'][$request->status_id]['state']; + if ($state == enum('status.states.close.id') || $state == enum('status.states.done.id')) { + //ToDo: is needed to check before state is done or close? + $request->merge([ + 'completed_at' => Carbon::now(), + 'work_finish' => Work::where('task_id', $task)->latest()->first()->ended_at ?? null + ]); + if (isset($taskModel->due_date) && $taskModel->due_date < date('yy-m-d')) { + //check if before set due date and miss, we change on time flag + $request->merge(['on_time' => false]); + } + } + } + if (!$request->has('tags')) { + $request->merge(['tags' => []]); + } + $taskModel->update($request->except('_business_info')); + return new TaskResource($taskModel); + } + + public function updateReadyToTest($business, $project, $task) + { + permit('isProjectGuest', ['project_id' => $project]); + $task = Task::where([['project_id', $project ], ['id', $task]])->firstOrFail(); + if ($task->assignee_id == \auth()->id()) { + $task->update([ + 'ready_to_test' => 1 + ]); + } else { + return abort(Response::HTTP_FORBIDDEN); // not allowed + } + return $task->load(['tagTask'=> fn($q) => $q->select('id', 'tag_id', 'task_id'), 'works', 'comments']); + } + + /** + * Rule's: + * 1) delete only not work time (soft delete) + */ + public function destroy($task) + { + $taskModel = Task::findOrFail($task); + if (Work::where('task_id', $task)->exists()) { + return \response()->json(['task_id' => 'The task id cannot be deleted.'], Response::HTTP_UNPROCESSABLE_ENTITY); + } + $taskModel->delete(); + return \response()->json(['message' => 'task deleted successfully.'], Response::HTTP_OK); + } + + public function toggleWatcher($business, $task, $project =null) + { + permit('isProjectGuest', ['project_id' => $project]); + $taskModel = Task::findOrFail($task); + $watchers = $taskModel->watchers ?? []; + + if (array_search(auth()->id(), $watchers) !== false) { + // remove auth from watchers + $new_watchers = array_values(\array_diff($watchers, [auth()->id()])); + } else { + // add auth to watchers + $new_watchers = array_merge($watchers, [auth()->id()]); + } + + $taskModel->update([ + 'watchers' => $new_watchers + ]); + return new TaskResource($taskModel); + } +} diff --git a/app/Http/Controllers/TaskFileController.php b/app/Http/Controllers/TaskFileController.php new file mode 100644 index 0000000..b8bd77c --- /dev/null +++ b/app/Http/Controllers/TaskFileController.php @@ -0,0 +1,87 @@ +id !== $project->business_id + || $project->id !== $task['project_id'] +// || $task['user_id']!== Auth::id() + ) { + \abort(Response::HTTP_UNAUTHORIZED); + } + + return [$business, $project, $task]; + } + + public function index(int $business, int $project, int $task) + { + // check permissions + // owner project + // admin project + // colleague project + // guest or de active + // return files as file resource + [$business, $project, $task] = $this->checkBelonging($business, $project, $task); + return FileResource::collection($task->files ?? []); + } + + public function sync(Request $request,int $business, int $project, int $task) + { + // different size and different validation + // validate + // validate the wallet is not so much in debt + // create record in the db + // put file in s3 + // return file resource + [$business, $project, $task] = $this->checkBelonging($business,$project,$task); + + $this->validate($request, [ + 'files' => 'required|array', + 'files.*' => 'int', + ]); + + $files = File::find($request->files)->each(function (File $file) { + if ($file->user_id !== Auth::id()) { + abort(Response::HTTP_UNAUTHORIZED); + } + }); + + // sync + + return FileResource::collection($files); + } + + public function download(int $business, int $project, int $task, int $file) + { + // requested file belongs to this project and this business + // check permisson + // create perma link or temp link + // return the file resource or stream it + [$business, $project, $task] = $this->checkBelonging($business, $project, $task); + + $file = File::findOrFail($file); + if ($file->user_id !== Auth::id()) { + abort(Response::HTTP_UNAUTHORIZED); + } + + return $file->getTemporaryLink(); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..b8a4135 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,70 @@ +filled('search')){ + $userQ = $userQ->where(function($query) use ($request) { + $query->where('email', 'like', '%'.$request->search.'%') + ->orWhere('username', 'like', '%'.$request->search.'%'); + }); + } + + return $userQ->where('id', '!=', auth()->id()) + ->whereNotIn('id', request('_business_info')['info']['users']->keys()) + ->select('id', 'name', 'email', 'username')->take($limit)->get(); + } + + public function index(Request $request) + { + $userQ = QueryBuilder::for(User::class) + ->allowedFilters([ + AllowedFilter::exact('id'), + ]); + return $userQ->select('id', 'name', 'email', 'username')->get(); + } + + public function show($user) + { + return User::select('id', 'name', 'email', 'username')->findOrFail($user); + } + + public function update(Request $request, string $user) + { + $user = User::findOrFail($user); + $user->update($request->all()); + + return $user; + } + + public function setAvatar(Request $request, string $user) + { + $user = User::findOrFail($user); + if ($request->hasFile('avatar')) { + $user->saveAsAvatar($request->file('avatar')); + } + + return $user; + } + + public function unSetAvatar(Request $request, string $user) + { + $user = User::findOrFail($user); + $user->deleteAvatar(); + $user->refresh(); + + return $user; + } +} diff --git a/app/Http/Controllers/WorkController.php b/app/Http/Controllers/WorkController.php new file mode 100644 index 0000000..d7f6d1f --- /dev/null +++ b/app/Http/Controllers/WorkController.php @@ -0,0 +1,242 @@ +limit > 100 ? 10 : $request->limit; + $workQ = $this->indexFiltering($business) + ->when($request->filled('group'), function ($q) use ($request) { + return $request->group == 'user' ? $q->report() : $q->reportByDate(); + }); + + return $request->filled('group') ? $workQ->get() : + $workQ->defaultSort('-id') + ->allowedSorts('id', 'started_at')->paginate($per_page); + } + + public function indexFiltering($business) + { + $query = Work::where('works.business_id', $business); + $workQ = queryBuilder::for($query) + ->join('tasks', 'tasks.id', 'works.task_id') + ->select('works.*', 'tasks.title', 'tasks.sprint_id', 'tasks.system_id') + ->allowedFilters([ + AllowedFilter::exact('project_id'), + AllowedFilter::exact('tasks.sprint_id', null, false), + AllowedFilter::exact('tasks.system_id', null, false), + AllowedFilter::exact('user_id'), + AllowedFilter::scope('started_at_in'), + AllowedFilter::scope('started_at'), + AllowedFilter::scope('spent_time_from'), + AllowedFilter::scope('spent_time_to'), + ]); + if (\request('_business_info')['info']['users'][\auth()->id()]['level'] != enum('levels.owner.id')) { + $requested_projects = isset(\request('filter')['project_id']) ? + array_unique(explode(',',\request('filter')['project_id'] ?? null )) : + null; + $requested_projects = collect($requested_projects)->keyBy(null)->toArray(); + $project_ids = $this->myStateProjects($requested_projects); + $workQ->where(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['non_guest_ids']) + ->orWhere(function ($q) use ($project_ids) { + $q->whereIn('project_id', $project_ids['guest_ids']) + ->where('assignee_id', auth()->id()); + }); + }); + } + return $workQ; + } + + public function myStateProjects($requested_projects) + { + $non_guest_ids = []; + $guest_ids = []; + $is_empty = empty($requested_projects); + + foreach (\request('_business_info')['info']['projects'] as $p_id => $p) { + + $level = \request('_business_info')['info']['projects'][$p_id]['members'][\auth()->id()]['level']; + + if (( $is_empty || isset($requested_projects[$p_id])) + && $level > enum('levels.guest.id')) { + array_push($non_guest_ids, $p_id); + } + if (( $is_empty || isset($requested_projects[$p_id])) + && $level == enum('levels.guest.id')) { + array_push($guest_ids, $p_id); + } + } + + return ['non_guest_ids' => $non_guest_ids, 'guest_ids' => $guest_ids]; + } + + public function show($business, $project, $task, $work) + { + permit('projectAccess', ['project_id' => $project]); + $work = Work::where([['project_id', $project ], ['task_id', $task], ['id', $work]])->firstOrFail(); + if (can('isDefiniteGuestInProject', ['project_id' => $project])){ // is guest in project (only guest) + return $work->user_id == \auth()->id() ? $work : abort(Response::HTTP_FORBIDDEN); // not allowed + } else { + return $work; + } + } + /** + * Rule's: + * 1) only assignee_id can store work + * 2) started_at after task created_at + * 3) ended_at after started_at + * 4) not any work before in this work + */ + public function store($business, $project, $task, Request $request) + { + $taskModel = Task::findOrFail($task); + if ($taskModel->assignee_id != auth()->id()) { + abort(Response::HTTP_FORBIDDEN); // not allowed + } + + $end = Carbon::createFromFormat('Y-m-d H:i', $request->ended_at); + $start = Carbon::createFromFormat('Y-m-d H:i', $request->started_at); + $diff_in_min = $end->diffInMinutes($start); + $work = Work::create($request->merge([ + 'business_id' => $business, + 'project_id' => $project, + 'task_id' => $task, + 'user_id' => auth()->id(), + 'minute_sum' => $diff_in_min, + 'task' => [ + 'spent_time' => $taskModel->spent_time + $diff_in_min + ] + ])->except('_business_info')); + $taskModel->refresh(); +// $taskModel->update([ +// 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null, +// 'spent_time' => $taskModel->spent_time + $diff_in_min +// ]); + return $taskModel->load(['tags', 'works', 'comments']); + } + + public function storeValidation($request, $taskModel) + { + $this->validate($request, [ + 'message' => 'nullable|string|min:3|max:225', + 'started_at' => 'required|date_format:Y-m-d H:i|after:'.$taskModel->created_at, + 'ended_at' => 'required|date_format:Y-m-d H:i|after:started_at', + ]); + $state = \request('_business_info')['workflows'][$taskModel->workflow_id]['statuses'][$taskModel->status_id]['state'] ?? null; + if ($state == enum('status.states.close.id') || $state == enum('status.states.done.id')) { + throw ValidationException::withMessages(['task' => 'The selected task is invalid.']); + } + $works = Work::where([ + ['ended_at', '>', $request->started_at], + ['ended_at', '<', $request->ended_at], + ])->orWhere([ + ['started_at', '>', $request->started_at], + ['started_at', '<', $request->ended_at], + ])->orWhere([ + ['started_at', '>=', $request->started_at], + ['ended_at', '<=', $request->ended_at], + ])->exists(); + if ($works) { + throw ValidationException::withMessages(['work' => 'The selected work is invalid.']); + } + } + + /** + * Rule's: + * 1) only assignee_id can store work + * 2) started_at after task created_at + * 3) ended_at after started_at + * 4) not any work before in this work + */ + public function update($business, $project, $task, $work, Request $request) + { + $taskModel = Task::findOrFail($task); + $workModel = Work::findOrFail($work); + if ($taskModel->assignee_id != auth()->id()) { + abort(Response::HTTP_FORBIDDEN); // not allowed + } + + $end = Carbon::createFromFormat('Y-m-d H:i', $request->ended_at); + $start = Carbon::createFromFormat('Y-m-d H:i', $request->started_at); + $new_diff_in_min = $end->diffInMinutes($start); + $old_diff_in_min = $workModel->minute_sum; + $workModel->update($request->merge([ + 'business_id' => $business, + 'project_id' => $project, + 'task_id' => $task, + 'user_id' => auth()->id(), + 'minute_sum' => $new_diff_in_min, + 'task' => [ + 'spent_time' => ($taskModel->spent_time - $old_diff_in_min) + $new_diff_in_min + ] + ])->except('_business_info')); + $taskModel->refresh(); +// $taskModel->update([ +// 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null, +// 'spent_time' => ($taskModel->spent_time - $old_diff_in_min) + $new_diff_in_min +// ]); + return $taskModel->load(['tags', 'works', 'comments']); + } + + public function updateValidation($request, $taskModel, $workModel) + { + $this->validate($request, [ + 'message' => 'nullable|string|min:3|max:225', + 'started_at' => 'nullable|date_format:Y-m-d H:i|after:'.$taskModel->created_at, + 'ended_at' => 'nullable|date_format:Y-m-d H:i|after:started_at', + ]); + //ToDo: is needed to check status is active or idea?? + $works = false; + if ($request->filled('started_at') || $request->filled('ended_at')) { + $started_at = $request->started_at ?? $workModel->started_at->format('Y-m-d H:i'); + $ended_at = $request->ended_at ?? $workModel->ended_at->format('Y-m-d H:i'); + if (strtotime($ended_at) <= strtotime($started_at)) { + throw ValidationException::withMessages(['ended_at' => 'The ended at must be a date after started at.']); + } + $works = Work::where([ + ['ended_at', '>', $started_at], + ['ended_at', '<', $ended_at], + ])->orWhere([ + ['started_at', '>', $started_at], + ['started_at', '<', $ended_at], + ])->orWhere([ + ['started_at', '>=', $started_at], + ['ended_at', '<=', $ended_at], + ])->where('id', '!=', $workModel->id)->exists(); + $end = Carbon::createFromFormat('Y-m-d H:i', $ended_at); + $start = Carbon::createFromFormat('Y-m-d H:i', $started_at); + \request()->merge(['minute_sum' => $end->diffInMinutes($start)]); + } + if ($works) { + throw ValidationException::withMessages(['work' => 'The selected work is invalid.']); + } + } + + public function destroy($business, $project, $task, $work) + { + $taskModel = Task::findOrFail($task); + $workModel = Work::findOrFail($work); + if ($taskModel->assignee_id != auth()->id()) { + abort(Response::HTTP_FORBIDDEN); // not allowed + } + $diff_in_min = $workModel->minute_sum; + $workModel->delete(); + $taskModel->update([ + 'work_start' => Work::where('task_id', $taskModel->id)->orderBy('started_at')->first()->started_at ?? null, + 'spent_time' => ($taskModel->spent_time - $diff_in_min) + ]); + return $taskModel->load(['tags', 'works','comments']); + } +} diff --git a/app/Http/Controllers/WorkflowController.php b/app/Http/Controllers/WorkflowController.php new file mode 100644 index 0000000..beff817 --- /dev/null +++ b/app/Http/Controllers/WorkflowController.php @@ -0,0 +1,67 @@ +merge(['business_id' => $business])->except('_business_info')); +// $statuses = collect($validatedData['statuses'])->map(function($status) use ($workflow, $business) { +// $status['business_id'] = $business; +// $status['workflow_id'] = $workflow->id; +// return $status; +// }); +// $statuses = $workflow->statuses()->createMany($statuses->toArray()); + return Business::info($business, true); + } + + public function update($business, $workflow, Request $request) + { + permit('businessWorkFlows'); + $workflowModel = Workflow::findOrFail($workflow); + $workflowModel->update($request->except('_business_info')); + return Business::info($business, true); + } + + public function syncStatus($business, $workflowModel) + { + $old_statuses_name = array_keys(collect(\request('_business_info')['workflows'][$workflowModel->id]['statuses'])->keyBy('name')->toArray()); + $new_statuses_name = array_keys(collect(\request('statuses'))->keyBy('name')->toArray()); + $removed_statuses_name = array_diff(array_merge($old_statuses_name, $new_statuses_name), $new_statuses_name); + + foreach ($removed_statuses_name as $status_name) { + //delete all statuses that removed name's from request->statuses + $workflowModel->statuses()->where('name', $status_name)->first()->delete(); + } + + foreach (request('statuses') as $status) { + //sync another statuses + $workflowModel->statuses() + ->updateOrCreate( + ['name' => $status['name'], 'business_id' => $business, 'workflow_id' => $workflowModel->id], + ['state' => $status['state'], 'order' => $status['order']] + ); + } + + return $workflowModel; + } + + public function delete($business, $workflow) + { + permit('businessWorkFlows'); + $workflow = Workflow::findOrFail($workflow); + foreach ($workflow->statuses as $status) { + //delete all statuses related to this workflow + $status->delete(); + } + $workflow->delete(); + return Business::info($business, true); + } + +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 30020a5..4bd9d18 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,6 +2,7 @@ namespace App\Http; +use App\Utilities\Middlewares\BindBusinessInfo; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel @@ -62,5 +63,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'bindBusiness' => BindBusinessInfo::class, ]; } diff --git a/app/Http/Resources/BusinessResource.php b/app/Http/Resources/BusinessResource.php new file mode 100644 index 0000000..b448dc5 --- /dev/null +++ b/app/Http/Resources/BusinessResource.php @@ -0,0 +1,34 @@ + 'user', + '_resource' => 'businesses', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'name': + case 'slug': + case 'wallet': + case 'files_volume': + case 'cache': + case 'calculated_at': + case 'created_at': + case 'updated_at': + case 'deleted_at': + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/CommentResource.php b/app/Http/Resources/CommentResource.php new file mode 100644 index 0000000..eecc44a --- /dev/null +++ b/app/Http/Resources/CommentResource.php @@ -0,0 +1,34 @@ + 'task', + '_resource' => 'comment', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'task_id' : + case 'user_id' : + case 'body' : + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/FileResource.php b/app/Http/Resources/FileResource.php new file mode 100644 index 0000000..bc99db1 --- /dev/null +++ b/app/Http/Resources/FileResource.php @@ -0,0 +1,35 @@ + 'user', + '_resource' => 'files', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'user_id': + case 'business_id': + case 'project_id': + case 'disk': + case 'original_name': + case 'name': + case 'extension': + case 'mime': + case 'size': + case 'description': + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/FingerprintResource.php b/app/Http/Resources/FingerprintResource.php new file mode 100644 index 0000000..2c2eded --- /dev/null +++ b/app/Http/Resources/FingerprintResource.php @@ -0,0 +1,34 @@ + 'user', + '_resource' => 'fingerprints', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'id': + case 'agent': + case 'ip': + case 'os': + case 'latitude': + case 'longitude': + case 'token': + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/ProjectResource.php b/app/Http/Resources/ProjectResource.php new file mode 100644 index 0000000..8800864 --- /dev/null +++ b/app/Http/Resources/ProjectResource.php @@ -0,0 +1,38 @@ + 'user', + '_resource' => 'project', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'name': + case 'business_id': + case 'slug': + case 'private': + case 'budget': + case 'start': + case 'finish': + case 'created_at': + case 'updated_at': + case 'archived_at': + case 'deleted_at': + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/TaskCollection.php b/app/Http/Resources/TaskCollection.php new file mode 100644 index 0000000..be5944b --- /dev/null +++ b/app/Http/Resources/TaskCollection.php @@ -0,0 +1,23 @@ + TaskResource::collection($this->collection), + 'now' => Carbon::now()->toDateString() + ]; + } +} diff --git a/app/Http/Resources/TaskResource.php b/app/Http/Resources/TaskResource.php new file mode 100644 index 0000000..94491fb --- /dev/null +++ b/app/Http/Resources/TaskResource.php @@ -0,0 +1,30 @@ +getAttributes() as $attribute => $value) { + $resource[$attribute] = $value; + if ($attribute == 'watchers') { + $resource[$attribute] = json_decode($value); + } + } + + $resource['tags'] = $this->tags()->pluck('tag_id')->toArray(); + $resource['works'] = $this->works; + $resource['comments'] = $this->comments; + + return $resource; + } +} diff --git a/app/Http/Resources/TransactionResource.php b/app/Http/Resources/TransactionResource.php new file mode 100644 index 0000000..89b07df --- /dev/null +++ b/app/Http/Resources/TransactionResource.php @@ -0,0 +1,32 @@ + 'user', + '_resource' => 'transactions', + ]; + + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'user_id': + case 'business_id': + case 'amount': + case 'succeeded': + case 'options': + case 'created_at': + case 'updated_at': + $resource[$attribute] = $value; + break; + } + } + + return $resource; + } +} diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 0000000..5bf3f1f --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,34 @@ + 'user', + '_resource' => 'user', + ]; + foreach ($this->getAttributes() as $attribute => $value) { + switch ($attribute) { + case 'id': + case 'name': + case 'mobile': + case 'email': + case 'created_at': + case 'updated_at': + $resource[$attribute] = $value; + break; + } + } + + + $resource['includes']['fingerprints'] = $this->whenLoaded('fingerprints', function () { + return FingerprintResource::collection($this->fingerprints); + }); + + return $resource; + } +} diff --git a/app/Listeners/ActivityRegistration.php b/app/Listeners/ActivityRegistration.php new file mode 100644 index 0000000..90b97b8 --- /dev/null +++ b/app/Listeners/ActivityRegistration.php @@ -0,0 +1,73 @@ +message)); + $message = json_decode($event->message); + Activity::create([ + 'business_id' => $message->business, + 'project_id' => $message->project, + 'actor_id' => $message->auth, + 'system_id' => $message->data->system_id, + 'workflow_id' => $message->data->workflow_id, + 'status_id' => $message->data->status_id, + 'sprint_id' => $message->data->sprint_id, + 'task_id' => $message->data->task_id ?? null, + 'subject_id' => $message->data->subject_id ?? null, + 'user_id' => $message->data->user_id, + 'crud_id' => $message->data->crud_id, + 'table_id' => enum('tables.'.$message->data->table_name.'.id'), + 'original' => $message->data->original, + 'diff' => $message->data->diff, + ]); + } +} diff --git a/app/Listeners/BusinessUpdateListener.php b/app/Listeners/BusinessUpdateListener.php new file mode 100644 index 0000000..3ef51cb --- /dev/null +++ b/app/Listeners/BusinessUpdateListener.php @@ -0,0 +1,32 @@ +message; + + $wallet = $payload?->data?->diff?->wallet; + $owners = Business::findOrFail($payload->business)->owners; + $message = ['body' => 'Test']; + + if ($wallet < 0) { + Notification::send($owners, new MailNotification($message)); + Notification::send($owners, new DBNotification($message)); + } + } +} diff --git a/app/Listeners/BusinessUserCreateNotif.php b/app/Listeners/BusinessUserCreateNotif.php new file mode 100644 index 0000000..29f4456 --- /dev/null +++ b/app/Listeners/BusinessUserCreateNotif.php @@ -0,0 +1,93 @@ +message; + if ($payload->data->original->level === enum('levels.inactive.id')) { + // When user level in business is zero, probably user added to business by system + // And not necessary send notification to stockholders + return; + } + $new_user = User::findOrFail($payload->data->original->user_id); + $owners = Business::findOrFail($payload->business)->owners()->where('id', '!=', $new_user->id)->get(); + + $notif = $this->makeNotif($payload,['business' => request('_business_info')['name'], 'user' => $new_user->name]); + + $users = $owners->prepend($new_user); + + $this->sendNotifications($users, $notif); + } + + /** + * Make notification object + * + * @param $payload + * @param array $options + * @return array + */ + public function makeNotif($payload, $options = []) { + return [ + 'greeting' => $this->getMessageLine($payload, 'greeting'), + 'subject' => $this->getMessageLine($payload, 'subject'), + 'title' => $this->getMessageLine($payload, 'title'), + 'body' => $this->getMessageLine($payload, 'body', $options) + ]; + } + + /** + * Fetch message from notifications lang file + * + * @param $payload + * @param $key + * @param null $options + * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Translation\Translator|string|null + * + */ + public function getMessageLine($payload, $key, $options = []) + { + return __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name').'.'.$key, $options); + } + + /** + * Call notifications + * + * @param $users + * @param $notif + */ + public function sendNotifications($users, $notif) { + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + Notification::send($users, new FcmNotification($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/ProjectUserCreateNotif.php b/app/Listeners/ProjectUserCreateNotif.php new file mode 100644 index 0000000..add26cb --- /dev/null +++ b/app/Listeners/ProjectUserCreateNotif.php @@ -0,0 +1,93 @@ +message; + + $new_user = User::findOrFail($payload->data->original->user_id); + $project = Project::findOrFail($payload->data->original->project_id); + $owners_id = request('_business_info')['info']['projects'][$project->id]['members']->reject(function ($item, $key) use ($new_user) { + return $item['level'] < enum('levels.owner.id') || $key === $new_user->id; + })->toArray(); + + $owners = User::whereIn('id', array_keys($owners_id))->get(); + + $notif = $this->makeNotif($payload,['project' => $project->name, 'user' => $new_user->name]); + + $users = $owners->prepend($new_user); + + $this->sendNotifications($users, $notif); + } + + /** + * Make notification object + * + * @param $payload + * @param array $options + * @return array + */ + public function makeNotif($payload, $options = []) { + return [ + 'greeting' => $this->getMessageLine($payload, 'greeting'), + 'subject' => $this->getMessageLine($payload, 'subject'), + 'title' => $this->getMessageLine($payload, 'title'), + 'body' => $this->getMessageLine($payload, 'body', $options) + ]; + } + + /** + * Fetch message from notifications lang file + * + * @param $payload + * @param $key + * @param null $options + * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Translation\Translator|string|null + * + */ + public function getMessageLine($payload, $key, $options = []) + { + return __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name').'.'.$key, $options); + } + + /** + * Call notifications + * + * @param $users + * @param $notif + */ + public function sendNotifications($users, $notif) { + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + Notification::send($users, new FcmNotification($notif)); + } +} 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/Listeners/TaskCreateNotif.php b/app/Listeners/TaskCreateNotif.php new file mode 100644 index 0000000..3bba2d8 --- /dev/null +++ b/app/Listeners/TaskCreateNotif.php @@ -0,0 +1,104 @@ +message; + + if ($payload->data->original->assignee_id !== null) { + $this->assigneeNotifHandler($payload); + } + if ($payload->data->original->approver_id !== null) { + $this->approverNotifHandler($payload); + } + } + + public function assigneeNotifHandler($payload) { + $user = User::findOrFail($payload->data->original->assignee_id); + $task = Task::findOrFail($payload->data->original->id); + + $notif = $this->makeNotif($payload, 'assignee', ['task' => $task->title]); + + $this->sendNotifications($user, $notif); + } + + public function approverNotifHandler($payload) { + $user = User::findOrFail($payload->data->original->approver_id); + $task = Task::findOrFail($payload->data->original->id); + + $notif = $this->makeNotif($payload, 'approver', ['task' => $task->title]); + + $this->sendNotifications($user, $notif); + } + + /** + * Make notification object + * + * @param $payload + * @param null $route + * @param array $options + * @return array + */ + public function makeNotif($payload, $route = null, $options = []) { + $route = $route == null ? "" : $route.'.'; + return [ + 'greeting' => $this->getMessageLine($payload, $route.'greeting'), + 'subject' => $this->getMessageLine($payload, $route.'subject'), + 'title' => $this->getMessageLine($payload, $route.'title'), + 'body' => $this->getMessageLine($payload, $route.'body', $options) + ]; + } + + /** + * Fetch message from notifications lang file + * + * @param $payload + * @param $key + * @param null $options + * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Translation\Translator|string|null + * + */ + public function getMessageLine($payload, $key, $options = []) + { + return __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name').'.'.$key, $options); + } + + /** + * Call notifications + * + * @param $users + * @param $notif + */ + public function sendNotifications($users, $notif) { + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + Notification::send($users, new FcmNotification($notif)); + } +} diff --git a/app/Listeners/TaskUpdateNotif.php b/app/Listeners/TaskUpdateNotif.php new file mode 100644 index 0000000..118036c --- /dev/null +++ b/app/Listeners/TaskUpdateNotif.php @@ -0,0 +1,155 @@ +message; + if (isset($payload->data->diff->assignee_id)) { + $this->assigneeNotifHandler($payload); + } + if (isset($payload->data->diff->approver_id)) { + $this->approverNotifHandler($payload); + } + if (isset($payload->data->diff->completed_at)) { + $this->completedNotifHandler($payload); + } + if (isset($payload->data->diff->ready_to_test)) { + $this->readyNotifHandler($payload); + } + } + + public function assigneeNotifHandler($payload) { + $user = User::findOrFail($payload->data->diff->assignee_id); + $task = Task::findOrFail($payload->data->original->id); + + $notif = $this->makeNotif($payload, 'assignee', ['task' => $task->title]); + + $this->sendNotifications($user, $notif); + } + + public function approverNotifHandler($payload) { + $user = User::findOrFail($payload->data->diff->approver_id); + $task = Task::findOrFail($payload->data->original->id); + + $notif = $this->makeNotif($payload, 'approver', ['task' => $task->title]); + + $this->sendNotifications($user, $notif); + } + + public function completedNotifHandler($payload){ + $task = Task::findOrFail($payload->data->original->id); + $user_ids = array_filter([ + $task->approver_id, + $task->assignee_id, + $task->creator_id + ]); + $user_ids = array_unique(array_merge($user_ids, $task->watchers)); + + $users = User::whereIn('id', $user_ids)->where('id', '!=', auth()->id())->get(); + + $notif = $this->makeNotif($payload, 'completed', ['task' => $task->title]); + + $this->sendNotifications($users, $notif); + } + + public function readyNotifHandler($payload) + { + $task = Task::findOrFail($payload->data->original->id); + $user_ids = array_unique(array_filter([ + $task->approver_id, + $task->assignee_id, + $task->creator_id + ])); + + $users = User::whereIn('id', $user_ids)->where('id', '!=', auth()->id())->get(); + + $notif = $this->makeNotif($payload, 'ready', ['task' => $task->title]); + + $this->sendNotifications($users, $notif); + } + + /** + * Make notification object + * + * @param $payload + * @param null $route + * @param array $options + * @return array + */ + public function makeNotif($payload, $route = null, $options = []) { + $route = $route == null ? "" : $route.'.'; + return [ + 'greeting' => $this->getMessageLine($payload, $route.'greeting'), + 'subject' => $this->getMessageLine($payload, $route.'subject'), + 'title' => $this->getMessageLine($payload, $route.'title'), + 'body' => $this->getMessageLine($payload, $route.'body', $options) + ]; + } + + /** + * Fetch message from notifications lang file + * + * @param $payload + * @param $key + * @param null $options + * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Translation\Translator|string|null + * + */ + public function getMessageLine($payload, $key, $options = []) + { + return __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name').'.'.$key, $options); + } + + /** + * Call notifications + * + * @param $users + * @param $notif + */ + public function sendNotifications($users, $notif) { +// switch ($level) { +// case "emergency": +//// Notification::send($users, new SmsNotification($notif)); +// case "critical": +// Notification::send($users, new MailNotification($notif)); +// case "high": +// Notification::send($users, new DBNotification($notif)); +// case "medium": +// Notification::send($users, new FcmNotification($notif)); +// case "low": +//// Notification::send($users, new SocketNotification($notif)); +// default: +//// Notification::send($users, new SocketNotification($notif)); +// } + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + Notification::send($users, new FcmNotification($notif)); + } +} diff --git a/app/Models/Activity.php b/app/Models/Activity.php new file mode 100644 index 0000000..ed20699 --- /dev/null +++ b/app/Models/Activity.php @@ -0,0 +1,38 @@ + 'array', + 'diff' => 'array', + ]; + + public function scopeCreatesBefore($query, $date) + { + return $query->whereDate('created_at', '<=', Carbon::parse($date)); + } + public function scopeCreatesAfter($query, $date) + { + return $query->whereDate('created_at', '>=', Carbon::parse($date)); + } + public function scopeCreatesIn($query, $days) + { + return $days != "" ? + $query->whereDate('created_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()) : + $query; + } + +} diff --git a/app/Models/Business.php b/app/Models/Business.php new file mode 100644 index 0000000..dc03aef --- /dev/null +++ b/app/Models/Business.php @@ -0,0 +1,487 @@ + 'business_user'] // relations [ name => pivot ] + ]; + + public $detach_relation = false; + + protected $casts = [ + 'has_avatar' => 'boolean', + 'calculated_at' => 'datetime', + ]; + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->id, + 'workflow_id' => null, + 'project_id' => null, + 'sprint_id' => null, + 'status_id' => null, + 'system_id' => null, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function rules() + { + return [ + 'name' => 'bail|required|string|min:2|max:255', + 'color' => 'nullable|string|min:2|max:255', + 'slug' => ['bail', 'required', 'string', 'min:2', 'max:255', 'regex:/^[a-zA-Z]+[a-zA-Z\d-]*(.*[a-zA-Z\d])+$/', + Rule::unique($this->table, 'slug')->ignore($this->id)], + 'description' => 'nullable|string|min:2|max:1000', + ]; + } + + public function cost() + { + return $this->hasMany(Cost::class, 'business_id','id'); + } + + public function owners() + { + return $this->users()->wherePivot('level', '=', enum('levels.owner.id')); + } + + public function members() + { + return $this->users()->wherePivot('owner', '!=', enum('levels.owner.id')); + } + + public function users() + { + return $this->belongsToMany( + User::class, 'business_user', 'business_id', 'user_id', + 'id', 'id', __FUNCTION__ + ) + ->using(ReportableRelation::class) + ->withPivot(self::$permissions); + } + + public function tags() + { + return $this->hasMany(Tag::class, 'business_id', 'id'); + } + + public function projects() + { + return $this->hasMany(Project::class, 'business_id', 'id'); + } + + public function systems() + { + return $this->hasMany(System::class, 'business_id', 'id'); + } + + public function workflows() + { + return $this->hasMany(Workflow::class, 'business_id', 'id'); + } + public function sprints() + { + return $this->hasMany(Sprint::class, 'business_id', 'id'); + } + + public function statuses() + { + return $this->hasMany(Status::class, 'business_id', 'id'); + } + + public function files() + { + return $this->hasMany(File::class, 'user_id', 'id'); + } + + public function transactions() + { + return $this->hasMany(Transaction::class, 'business_id', 'id'); + } + + public function updateRelations() + { + // users relations + if (!empty($this->filled_relations['users']) || $this->detach_relation) { + $this->dirties['users'] = $this->users()->sync($this->filled_relations['users'], $this->detach_relation); + } + } + + + public static function info($businessId, $fresh = false) + { + + $info = []; + + $fresh = true; + if ($fresh){ + Cache::forget('business_info'.$businessId); + } + + if (Cache::has('business_info'.$businessId)) { + return Cache::get('business_info'.$businessId); + } else { + $business = self::findOrFail($businessId); + + $tags = $business->tags()->select('id', 'label', 'color')->get()->keyBy('id'); + $info['tags'] = $tags->pluck('id'); + + $workflows = $business->workflows()->select ('id', 'business_id', 'name','desc')->get()->keyBy('id') + ->load(['statuses'=> fn($q) => $q->select('id', 'business_id', 'workflow_id', 'name', 'state', 'order')]) + ->map(fn($q) => [ + 'id' => $q->id, + 'business_id' => $q->business_id, + 'name' => $q->name, + 'desc' => $q->desc, + 'statuses' => $q['statuses']->keyBy('id'), + ]); + + $info['workflows'] = $workflows->map(fn($q) => ['statuses' => $q['statuses']->pluck('id')]); + + + $users = $business->users()->select('id', 'name', 'email', 'mobile', 'username')->with('media')->get() + ->keyBy('id') + ->map(fn($u) => [ + 'id' => $u->id, + 'name' => $u->name, + 'email' => $u->email, + 'mobile' => $u->mobile, + 'username' => $u->get, + 'avatar' => $u->has_avatar, + 'level' => $u->pivot['level'], + ]); + + $info['users'] = $users->map(fn($u) => [ + 'level' => $u['level'] + ]); + + $projects = $business->projects()->get()->keyBy('id')->load([ + 'members' => fn($q) => $q->select('id', 'level'), + 'systems' => fn($q) => $q->select('id', 'project_id', 'name'), + 'sprints' => fn($q) => $q->select('id', 'project_id', 'name', 'description', 'started_at', 'ended_at', 'active'), + 'media', + ]); + + $info['projects'] = $projects->map(function($q) use($users){ + return [ + 'avatar' => $q->has_avatar, + 'systems' => $q->systems->pluck('id'), + 'sprints' => $q->sprints->pluck('id'), + 'members' => $users->keyBy('id')->map(function($u, $uid) use ($q){ + $project_user = $q->members->firstWhere('id', $uid); + return $project_user? ['level' => $project_user->pivot->level, 'is_direct' => true]: + ['level' => $q->private && $u['level'] != enum('levels.owner.id') ? enum('levels.inactive.id') : $u['level'], + 'is_direct'=> $project_user ? true : false]; + }) + ]; + }); + + $business_info = array_merge( + $business->only('id', 'name', 'slug', 'color','wallet', 'files_volume'), + ['avatar' => $business->has_avatar], + compact( + 'info', + 'tags', + 'workflows', + 'users', + 'projects' + ) + ); + + Cache::put('business_info'.$businessId , $business_info, config('app.cache_ttl')); + + return $business_info; + } + + + } + + public static function stats() + { + return [ + 'users' => [ + 10 => [ + 'statuses' => [ + 10 => 20, + 30 => 40 + ], + 'workflows' => [ + 10 => 20, + 30 => 40 + ], + 'tags' => [ + 10 => 20, + 30 => 40 + ], + 'project' => [ + 10 => 20, + 30 => 40 + ], + + 'sprints' => [ + 10 => 20, + 30 => 40 + ], + 'works' => [ + ], + '__subsystems' => [ + 10 => 20, + 30 => 40 + ], + ] + ], + 'workflows' => [ + 50 => [ + 'statuses' => [ + 20 => 50 + ], + + ] + ], + 'statuses' => [ + 10 => [ + 'users' => [ + + ], + 'projects' => [ + + ] + ] + ], + 'sprints' => [ + 10 => [ + 'statuses' => [ + 10 => [ + 10 => 1 + ] + ] + ] + ] + ]; + } + + public function reportActivity() + { + + } + + + public static function nuxtInfo($businessId) + { + if (empty($businessId)){ + return null; + } + + Cache::forget('business_nuxt_info' . $businessId); + return Cache::rememberForever('business_nuxt_info' . $businessId, function () use ($businessId) { + + $business = self::with([ + 'projects.members' => fn($q) => $q->select('id', 'name'), + 'projects', + 'tags', + 'workflows.statuses', + 'workflows', + 'statuses', + 'users', + ])->findOrFail($businessId); + + + $globals = []; + $business->users->each(function ($u) use (&$globals) { + $globals[$u->id] = $u->pivot->owner == true ? ['owner' => true] : $u->pivot->only(self::$permissions); + }); + + + $projects = []; + $business->projects->each(function ($p) use (&$projects, &$globals) { + + }); + + + $business->setRelation('projects', collect($projects)); + $business->setRelation('users', collect($globals)); + + return $business; + }); + + } + + + public static function infoOld($businessId) + { + + if (empty($businessId)) + return null; + + + Cache::forget('business_info' . $businessId); + return Cache::rememberForever('business_info' . $businessId, function () use ($businessId) { + $ob = []; + $business = self::with([ + 'projects.members' => fn($q) => $q->select('id', 'name'), + 'projects' => fn($q) => $q->select('id', 'business_id', 'private'), + 'tags' => fn($q) => $q->select('id', 'business_id', 'label'), + 'sprints' => fn($q) => $q->select('id','business_id','name', 'active'), + 'workflows.statuses' => fn($q) => $q->select('id', 'name'), + 'workflows' => fn($q) => $q->select('id', 'business_id', 'name'), + 'statuses' => fn($q) => $q->select('id', 'business_id', 'name', 'state'), + 'users' => fn($q) => $q->select('id', 'name'), + ])->findOrFail($businessId); + + + // permissions in business + $permissions = []; + $business->users->each(function ($user) use (&$permissions) { + $permissions[$user->id] = $user->pivot->only(['owner']); + $permissions[$user->id]['global_PA'] = $permissions[$user->id]['owner'] == true ? + enum('roles.manager.id') : $user->pivot->PA; + $permissions[$user->id]['global_FA'] = $permissions[$user->id]['owner'] == true ? + enum('roles.manager.id') : $user->pivot->FA; + $permissions[$user->id]['PA'] = []; + $permissions[$user->id]['FA'] = []; + }); + + + //projects + $projects = []; + $business->projects->each(function ($p) use (&$permissions, $business, &$projects) { + + $business->users->each(function ($user) use (&$permissions, $p) { + $PA = null; + $FA = null; + + if ($permissions[$user->id]['owner'] == true) { + $PA = enum('roles.manager.id'); + $FA = enum('roles.manager.id'); + } else if (!empty($project_user = $p->getPermissions($user->id))) { + $PA = $project_user->pivot->PA; + $FA = $project_user->pivot->FA; + } else if (empty($p->getPermissions($user->id))) { + $PA = $p->private == false ? $permissions[$user->id]['global_PA'] : enum('roles.hidden.id') ; + $FA = $p->private == false ? $permissions[$user->id]['global_FA'] : enum('roles.hidden.id'); + } + + + if ($PA !== null && $FA !== null) { + $permissions[$user->id]['PA'][$p->id] = $PA; + $permissions[$user->id]['FA'][$p->id] = $FA; + } + }); + + $projects[$p->id] =''; + }); + + + //workflow + $workflows = []; + $business->workflows->each(function ($w) use (&$workflows) { + $workflows[$w->id] = $w->statuses->pluck('id'); + }); + + + $ob['tags'] = $business->tags->pluck('id'); + $ob['statuses'] = $business->statuses; + $ob['sprints'] = $business->sprints; + $ob['workflows'] = $workflows; + $ob['users'] = $permissions; + $ob['projects'] = $projects; + + return collect($ob); + }); + + } + + public function registerMediaCollections(): void + { + $this->addMediaCollection(static::COLLECTION_NAME) + ->acceptsMimeTypes([ + 'image/jpeg', + 'image/png', + 'image/tiff', + 'image/gif', + ]) + ->useDisk('public') + ->singleFile(); + } + + public function registerMediaConversions(Media $media = null): void + { + $this->addMediaConversion(static::CONVERSION_NAME) + ->width(200) + ->height(200) + ->queued() + ->nonOptimized() + ->performOnCollections(static::COLLECTION_NAME); + } + + + public function saveAsAvatar(UploadedFile $avatar): void + { + $this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME); + $this->update([ + 'has_avatar' => true, + ]); + @unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath()); + } + + public function deleteAvatar(): void + { + $path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath(); + $this->getFirstMedia(static::COLLECTION_NAME)->delete(); + $this->update([ + 'has_avatar' => false, + ]); + @unlink($path); + } + + public function getAvatarUrl(): ?string + { + if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) { + return $url; + } + + return null; + } +} diff --git a/app/Models/Comment.php b/app/Models/Comment.php new file mode 100644 index 0000000..ea1c5f8 --- /dev/null +++ b/app/Models/Comment.php @@ -0,0 +1,19 @@ + 'required|string|min:3|max:1000', + ]; + } +} diff --git a/app/Models/Cost.php b/app/Models/Cost.php new file mode 100644 index 0000000..98b2e63 --- /dev/null +++ b/app/Models/Cost.php @@ -0,0 +1,28 @@ + 'array', + 'tax' => 'float', + ]; + + public function business() + { + return $this->belongsTo(Business::class, 'business_id'); + } +} diff --git a/app/Models/File.php b/app/Models/File.php new file mode 100644 index 0000000..2c4c56d --- /dev/null +++ b/app/Models/File.php @@ -0,0 +1,75 @@ + 'required', + 'business_id' => 'required', + 'project_id' => 'required', + 'original_name' => 'required', + 'name' => 'required', + 'extension' => 'required', + 'size' => 'required', + 'description' => 'nullable', + ]; + } + + + public function user() + { + return $this->belongsTo(User::class, 'user_id','id','id',__FUNCTION__); + } + + public function business() + { + return $this->belongsTo(Business::class, 'business_id', 'id', 'id', __FUNCTION__); + } + + public function project() + { + return $this->belongsTo(Project::class, 'project_id', 'id', 'id', __FUNCTION__); + } + + public function updateRelations() + { + + } + + public function reportActivity() + { + + } + + public function getPath() + { + return $this->business->id . \DIRECTORY_SEPARATOR . $this->project->id . DIRECTORY_SEPARATOR . $this->name; + } + + public function getTemporaryLink() + { + return Storage::disk('s3')->temporaryUrl( + $this->getPath(), + \Carbon\Carbon::now()->addMinutes(15), + [ + 'Content-Type' => $this->mime, + 'Content-Disposition' => $this->original_name, + ] + ); + } +} diff --git a/app/Models/Fingerprint.php b/app/Models/Fingerprint.php new file mode 100644 index 0000000..f6cb0d4 --- /dev/null +++ b/app/Models/Fingerprint.php @@ -0,0 +1,31 @@ +belongsTo(User::class, 'user_id', 'id', __FUNCTION__); + } + + public function rules() + { + return [ + 'user_id' => 'required|integer|exists:users,id', + 'agent' => 'required|string', + 'ip' => 'required|ip', + 'os' => 'required|string', + 'latitude' => 'required', + 'longitude' => 'required', + 'token' => 'required|string|min:60', + 'fcm_token' => 'nullable', + ]; + } +} diff --git a/app/Models/Model.php b/app/Models/Model.php new file mode 100644 index 0000000..e47e36d --- /dev/null +++ b/app/Models/Model.php @@ -0,0 +1,237 @@ +action = static::CREATED; + }); + + static::updated(function ($model) { + $model->action = static::UPDATED; + }); + + static::deleted(function ($model) { + $model->action = static::DELETED; + }); + } + + /** + * @return void + * @throw \Exception + */ + public function rules() + { + return []; + } + + /** + * + * + * @param array $attributes + * @return void + */ + public function validate(array $attributes = null) + { + $attributes = $attributes ?? $this->getAttributes(); + + /** @var Validator $validator */ + $validator = app('validator')->make($attributes, $this->rules()); + + if ($validator->fails()) { + throw new ValidationException( + $validator, + new JsonResponse($validator->errors()->getMessages(), Response::HTTP_UNPROCESSABLE_ENTITY) + ); + } + } + + /** + * @return void + */ + public function updateRelations() + { + } + + /** + * @param string|null $key + * @return void + */ + public function getValueOf(?string $key) + { + $values = []; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + protected function makeChanges() + { + if (empty($this->reportable)) { + return; + } + + $changes = new Collection($this->getDirty()); + + // fillable * or field + $changes = $changes->filter(function ($value, $key) { + foreach ($this->reportable as $i => $name) { + if ($key === $name) { + return true; + } + } + return false; + }); + + if (($changes->isEmpty() && $this->action == static::UPDATED)) { + return; + } + + return [ + 'original' => $this->getOriginal() + $this->getAttributes(), + 'diff' => $changes->toArray(), + ]; + +// return [ +// 'auth' => Auth::id(), +// 'timestamp' => $this->freshTimestamp(), +// 'business' => $this->getValueOf('business_id'), +// 'info' => \request('_business_info')['info'] ?? null, +// 'project' => $this->getValueOf('project_id'), +// 'data' => [ +// 'sprint_id' => $this->getValueOf('sprint_id'), +// 'system_id' => $this->getValueOf('system_id'), +// 'workflow_id' => $this->getValueOf('workflow_id'), +// 'status_id' => $this->getValueOf('status_id'), +// 'user_id' => $this->getValueOf('user_id'), +// 'table_name' => $this->getTable(), +// 'crud_id' => $this->action, +// 'original' => $this->getOriginal() + $this->getAttributes(), +// 'diff' => $changes->toArray(), +// ], +// 'from' => env('CONTAINER_NAME'), +// ]; + } + + protected function report($changes): void + { + if ($this->action == null){ + return; + } + $payload = [ + 'auth' => Auth::id(), + 'timestamp' => $this->freshTimestamp(), + 'business' => $this->getValueOf('business_id'), + 'info' => \request('_business_info') ?? null, + 'project' => $this->getValueOf('project_id'), + 'data' => [ + 'sprint_id' => $this->getValueOf('sprint_id'), + 'system_id' => $this->getValueOf('system_id'), + 'workflow_id' => $this->getValueOf('workflow_id'), + 'status_id' => $this->getValueOf('status_id'), + 'task_id' => $this->getValueOf('task_id'), + 'subject_id' => $this->getValueOf('subject_id'), + 'user_id' => $this->getValueOf('user_id'), + 'table_name' => $this->getTable(), + 'crud_id' => $this->action, + 'original' => $changes['original'] + $this->getOriginal(), + 'diff' => $changes['diff'], + ], + 'from' => env('CONTAINER_NAME'), + ]; + + ModelSaved::dispatch(json_encode($payload)); + } + + /** + * @param array $options + * @return void + */ + public function save(array $options = []) + { + // The validation function is called first + $this->validate(); + + // Then, because the relationships are set as attributes in this model + // we pre-enter their names in filled_relation attribute and store + // them in a temporary variable with a loop. + foreach ($this->fillable_relations as $relation) { + $this->filled_relations[$relation] = $this[$relation]; + unset($this[$relation]); + } + + // all of its action inside one transaction + // so if any of them failed the whole + // process rollbacked + DB::transaction(function () use ($options) { + // report to the activity aggregator + $changes = $this->makeChanges(); + + // save the model with it's attributes + parent::save($options); + + // save the model with it's relationships + $this->updateRelations(); + + is_array($changes) ? $this->report($changes) : true; + }, 3); + } + + public function delete() + { + $changes = $this->makeChanges(); + parent::delete(); + is_array($changes) ? $this->report($changes) : true; + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php new file mode 100644 index 0000000..62acd0a --- /dev/null +++ b/app/Models/Project.php @@ -0,0 +1,206 @@ + 'project_user'] + ]; + + protected $fillable_relations = ['members']; + + public $detach_relation = false; + + public function rules() + { + return [ + 'name' => 'required|string|min:2|max:225', + 'slug' => ['required', 'string', 'min:2', 'max:225', + Rule::unique($this->table, 'slug') + ->where('business_id', request('business_id')) + ->whereNull('deleted_at') + ->ignore($this->id)], + 'order' => 'nullable|numeric|min:0', + 'private' => 'nullable|boolean', + 'color' => 'nullable|string|min:2|max:255', + 'active' => 'nullable|boolean', + 'description' => 'nullable|string|min:2|max:1000', + // 'members' => empty($this->id) ? '' : 'required|array' + ]; + } + + protected $casts = [ + 'private' => 'boolean', + 'start' => 'date', + 'finish' => 'date', + 'has_avatar' => 'boolean', + ]; + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => $this->id, + 'sprint_id' => null, + 'workflow_id' => null, + 'status_id' => null, + 'system_id' => null, + 'actor_id' => auth()->id(), + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + public function members() + { + $permissions = self::$permissions; + return $this->belongsToMany(User::class)->using(ReportableRelation::class) + ->withPivot($permissions); + } + + public function owners() + { + return $this->members()->wherePivot('level', '=', enum('levels.owner.id')); + } + + public function tasks() + { + return $this->hasMany(Task::class, 'project_id', 'id'); + } + + public function business() + { + return $this->belongsTo(Business::class, 'business_id', 'id'); + } + + public function systems() + { + return $this->hasMany(System::class, 'project_id', 'id'); + } + + public function sprints() + { + return $this->hasMany(Sprint::class, 'project_id', 'id'); + } + + public function files() + { + return $this->hasMany(File::class, 'user_id', 'id'); + } + + public function updateRelations() + { + // members +// if (!empty($this->filled_relations['members']) || $this->detach_relation) { +// $this->dirties['members'] = $this->members()->sync($this->filled_relations['members'], $this->detach_relation); +// } + } + + public function reportActivity() + { +// foreach ($this->dirties as $name => $value) { +// return \post('task', 'task/v1/log', [ +// 'user_id' => Auth::id(), +// 'business_id' => $this->business_id, +// 'loggable_id' => $this->id, +// 'loggable_type' => $this->getTypeId(), +// 'action' => $this->getAction(), // id of the action +// 'data' => [$name => [ +// 'original' => $value['original'], +// 'diff' => $value['diff'], +// ]], +// ]); +// } + } + + public function getPermissions($user_id) + { + return $this->members->where('id',$user_id)->first(); + } + + public function registerMediaCollections(): void + { + $this->addMediaCollection(static::COLLECTION_NAME) + ->acceptsMimeTypes([ + 'image/jpeg', + 'image/png', + 'image/tiff', + 'image/gif', + ]) + ->useDisk('public') + ->singleFile(); + } + + public function registerMediaConversions(Media $media = null): void + { + $this->addMediaConversion(static::CONVERSION_NAME) + ->width(200) + ->height(200) + ->queued() + ->nonOptimized() + ->performOnCollections(static::COLLECTION_NAME); + } + + + public function saveAsAvatar(UploadedFile $avatar): void + { + $this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME); + $this->update([ + 'has_avatar' => true, + ]); + @unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath()); + } + + public function deleteAvatar(): void + { + $path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath(); + $this->getFirstMedia(static::COLLECTION_NAME)->delete(); + $this->update([ + 'has_avatar' => false, + ]); + @unlink($path); + } + + public function getAvatarUrl(): ?string + { + if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) { + return $url; + } + + return null; + } +} diff --git a/app/Models/ReportableRelation.php b/app/Models/ReportableRelation.php new file mode 100644 index 0000000..f525bf0 --- /dev/null +++ b/app/Models/ReportableRelation.php @@ -0,0 +1,140 @@ +action = static::CREATED; + static::report($model); + }); + static::updated(function ($model) { + $model->action = static::UPDATED; + static::report($model); + }); + static::deleted(function ($model) { + $model->action = static::DELETED; + static::report($model); + }); + } + + public static function report($model) + { + $payload = [ + 'auth' => Auth::id(), + 'timestamp' => $model->freshTimestamp(), + 'business' => $model->pivotParent->getValueOf('business_id'), + 'info' => \request('_business_info') ?? null, + 'project' => $model->pivotParent->getValueOf('project_id'), + 'data' => [ + 'sprint_id' => $model->pivotParent->getValueOf('sprint_id'), + 'system_id' => $model->pivotParent->getValueOf('system_id'), + 'workflow_id' => $model->pivotParent->getValueOf('workflow_id'), + 'status_id' => $model->pivotParent->getValueOf('status_id'), + 'task_id' => $model->pivotParent->getValueOf('task_id'), + 'user_id' => $model->user_id, + 'table_name' => $model->getTable(), + 'crud_id' => $model->action, + 'original' => $model->getOriginal() + $model->getAttributes(), + 'diff' => $model->getChanges(), + ], + 'from' => env('CONTAINER_NAME'), + ]; + + 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() + { + return $properties = [ + // Message properties + 'Persistent' => '1', // Marks a message as persistent + + // those familiar with the protocol may choose to use this property instead of Persistent. They control the same thing. + // Non-persistent (1) or persistent (2). + 'DeliveryMode' => '', + + // Used to describe the mime-type of the encoding. For example for the often used JSON encoding it is a good practice to set this property to: application/json. + // MIME content type. + // short string (max. 256 characters) + 'content_type' => '', + + // Commonly used to name a callback queue. + // Address to reply to. + // short string (max. 256 characters) + 'reply_to' => '', + + // Useful to correlate RPC responses with requests. + // Application correlation identifier. + // short string (max. 256 characters) + 'correlation_id' => '', + + /** Rarley Used Properties */ + + // This defines the message priority + // Message priority, 0 to 9 + 'priority' => '', + + // This is the message time stamp + // Message timestamp. + 'timestamp' => '', + + // This is the broker with whom the user sends the message (by default, it is "guest"). + // Creating user id. + // short string (max. 256 characters) + 'user_id' => '', + + // MIME content encoding. + // short string (max. 256 characters) + 'content_encoding' => '', + + // Message expiration specification. + // short string (max. 256 characters) + 'expiration' => '', + + // Application message identifier. + // short string (max. 256 characters) + 'message_id' => '', + + // Message type name. + // short string (max. 256 characters) + 'type' => '', + + // Creating application id. + // short string (max. 256 characters) + 'app_id' => '', + 'cluster_id' => '', + ]; + } +} diff --git a/app/Models/SoftDeletes.php b/app/Models/SoftDeletes.php new file mode 100644 index 0000000..5b3374b --- /dev/null +++ b/app/Models/SoftDeletes.php @@ -0,0 +1,29 @@ +forceDeleting) { + $this->exists = false; + + return $this->setKeysForSaveQuery($this->newModelQuery())->forceDelete(); + } + + $time = $this->freshTimestamp(); + + $this->{$this->getDeletedAtColumn()} = $time; + + if ($this->timestamps && !is_null($this->getUpdatedAtColumn())) { + $this->{$this->getUpdatedAtColumn()} = $time; + } + + return $this->save(); + } +} diff --git a/app/Models/Sprint.php b/app/Models/Sprint.php new file mode 100644 index 0000000..21cc40f --- /dev/null +++ b/app/Models/Sprint.php @@ -0,0 +1,70 @@ + 'bail|required|string|min:2|max:225', + 'started_at' => 'bail|required|date|date_format:Y-m-d', + 'ended_at' => 'bail|required|date|date_format:Y-m-d|after:started_at', + 'active' => 'nullable|boolean', + 'description' => 'nullable|string|min:2|max:1000', + ]; + } + + protected $casts = [ + 'active' => 'boolean' + ]; + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => $this->project_id, + 'sprint_id' => $this->id, + 'workflow_id' => null, + 'status_id' => null, + 'system_id' => null, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + + public function business() + { + return $this->belongsTo(Business::class, 'business_id', 'id'); + } + + public function projects() + { + return $this->belongsTo(Project::class, 'project_id', 'id'); + } + + public function tasks() + { + return $this->hasMany(Task::class, 'sprint_id', 'id'); + } + +} diff --git a/app/Models/Status.php b/app/Models/Status.php new file mode 100644 index 0000000..1bf3b26 --- /dev/null +++ b/app/Models/Status.php @@ -0,0 +1,53 @@ +'required|string|min:3|max:255', + 'state' => 'nullable|between:0,3', + 'order' => 'nullable|numeric|min:0' + ]; + } + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => null, + 'sprint_id' => null, + 'workflow_id' => $this->workflow_id, + 'status_id' => $this->id, + 'system_id' => null, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function workflow() + { + return $this->belongsTo(Workflow::class,'workflow_id','id'); + } +} diff --git a/app/Models/System.php b/app/Models/System.php new file mode 100644 index 0000000..331f2f6 --- /dev/null +++ b/app/Models/System.php @@ -0,0 +1,66 @@ + 'required|string|min:2|max:225', + ]; + } + + protected $casts = [ + 'private' => 'boolean' + ]; + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => $this->project_id, + 'sprint_id' => null, + 'workflow_id' => null, + 'status_id' => null, + 'system_id' => $this->id, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + + public function business() + { + return $this->belongsTo(Business::class, 'business_id', 'id'); + } + + public function project() + { + return $this->belongsTo(Project::class, 'project_id', 'id'); + } + + + public function tasks() + { + return $this->hasMany(Task::class, 'sub_project_id', 'id'); + } + +} diff --git a/app/Models/Tag.php b/app/Models/Tag.php new file mode 100644 index 0000000..308359d --- /dev/null +++ b/app/Models/Tag.php @@ -0,0 +1,56 @@ + $this->business_id, + 'project_id' => null, + 'sprint_id' => null, + 'workflow_id' => null, + 'status_id' => null, + 'system_id' => null, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function rules() + { + return [ + 'label' => 'required|string|min:3|max:225', + 'color' => 'nullable|string|min:2|max:255', + ]; + } + + public function business() + { + return $this->belongsTo(Business::class,'business_id','id',__FUNCTION__); + } + + public function task() + { + return $this->belongsToMany( + Task::class,'tag_task','tag_id','task_id', + 'id','id',__FUNCTION__ + ); + } +} diff --git a/app/Models/TagTask.php b/app/Models/TagTask.php new file mode 100644 index 0000000..1f2dd20 --- /dev/null +++ b/app/Models/TagTask.php @@ -0,0 +1,12 @@ + 'tag_task'] + ]; + protected $fillable_relations = [ + 'tags' + ]; + + protected $casts = [ + 'work_start' => 'datetime:Y-m-d H:i', + 'work_finish' => 'datetime:Y-m-d H:i', + 'watchers' => 'array', + ]; + + public function rules() + { +// dd(\request('_business_info')['info']['workflows']->toArray()); + $validations = [ + 'assignee_id' => ['nullable', 'numeric', + function ($attribute, $value, $fail) { + //check assignee at least guest in project + if (!can('isProjectGuest', ['project_id' => request()->route('project'), 'user_id' => $value])) { + $fail('The selected '.$attribute.' is invalid.'); + } + if (request()->method() === 'PUT' && ($this->assignee_id != $value && Work::where('task_id', $this->id)->exists())) { + //when update execute + //check if change assignee then should be on task not work set + $fail('The selected '.$attribute.' is invalid.'); + } + }], + 'system_id' => ['nullable', 'numeric', + Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['systems'])], + 'sprint_id' => ['nullable', 'numeric', + Rule::in(\request('_business_info')['info']['projects'][request()->route('project')]['sprints'])], + 'workflow_id' => ['required', 'numeric', + Rule::in(array_keys(\request('_business_info')['info']['workflows']->toArray()))], + 'status_id' => 'required|numeric', + 'approver_id' => ['nullable', 'numeric', + function ($attribute, $value, $fail) { + //check approval at least colleague in project + if (!can('isProjectColleague', ['project_id' => request()->route('project'), 'user_id' => $value])) { + $fail('The selected '.$attribute.' is invalid.'); + } + }], + 'title' => 'required|string|min:3|max:254', + 'description' => 'nullable|string|min:2|max:1000', + 'priority' => 'nullable|numeric|between:1,10', + 'estimated_time' => 'bail|nullable|numeric', + 'due_date' => 'bail|nullable|date|date_format:Y-m-d|after_or_equal:'. + ((request()->method() === 'POST') ? date('yy-m-d') : $this->created_at->toDateString()), + 'tags' => 'nullable|array', + 'tags.*' => [new RequiredIf(isset($request->tags)), 'numeric', + Rule::in(\request('_business_info')['info']['tags'])], + ] ; + $workflow_id = $this->workflow_id; + if (request()->filled('workflow_id') && isset(request('_business_info')['info']['workflows'][request()->workflow_id])) { + $workflow_id = request()->workflow_id; + } + if (isset($workflow_id)) { + $validations['status_id'] = ['bail', 'required', 'numeric', + //check status exists in status list + Rule::in(\request('_business_info')['info']['workflows'][$workflow_id]['statuses']), + function ($attribute, $value, $fail) use ($workflow_id) { + //check not close or done + $state = \request('_business_info')['workflows'][$workflow_id]['statuses'][$value]['state']; + if (request()->method() == 'POST' && ($state == enum('status.states.close.id') || $state == enum('status.states.done.id'))) { + $fail('The selected '.$attribute.' is invalid.'); + } + if ((request()->method() == 'PUT') && !can('projectTasks', ['project_id' => request()->route('project')]) + && $state != enum('status.states.active.id')) { + $fail('The selected '.$attribute.' is invalid.'); + } + }]; + } + + return $validations; + } + public function updateRelations() + { + // tags relations + $this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']); + + //old code +// if (!empty($this->filled_relations['tags'])) { +// $this->dirties['tags'] = $this->tags()->sync($this->filled_relations['tags']); +// } + + } + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => $this->project_id, + 'sprint_id' => $this->sprint_id, + 'workflow_id' => $this->workflow_id, + 'status_id' => $this->status_id, + 'system_id' => $this->system_id, + 'task_id' => $this->id, + 'subject_id' => $this->id, + 'user_id' => $this->assignee_id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function business() + { + return $this->belongsTo(Business::class, 'business_id', 'id', __FUNCTION__); + } + + public function creator() + { + return $this->belongsTo(User::class, 'creator_id', 'id', __FUNCTION__); + } + + public function user() + { + return $this->belongsTo(User::class, 'user_id', 'id', __FUNCTION__); + } + + public function project() + { + return $this->belongsTo(Project::class, 'project_id', 'id', __FUNCTION__); + } + + public function tags() + { + return $this->belongsToMany(Tag::class)->using(ReportableRelation::class); + } + + // Todo: are we need this relation???? + public function tagTask() + { + return $this->hasMany(TagTask::class, 'task_id', 'id'); + } + + public function works() + { + return $this->hasMany(Work::class, 'task_id', 'id'); + } + + public function comments() + { + return $this->hasMany(Comment::class, 'task_id', 'id'); + } + + public function scopePriorityMin($query, $min) + { + return $query->where('tasks.priority', '>=', $min); + } + public function scopePriorityMax($query, $max) + { + return $query->where('tasks.priority', '<=', $max); + } + public function scopeCreatesBefore($query, $date) + { + //ToDo: comment lines have better performance but should be test +// return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date.' 23:59:59')); +// return $query->where('tasks.created_at', '<', (new DateTime('2014-07-10'))->modify('+1 day')->format('Y-m-d')); + return $query->whereDate('tasks.created_at', '<=', Carbon::parse($date)); + } + public function scopeCreatesAfter($query, $date) + { + return $query->whereDate('tasks.created_at', '>=', Carbon::parse($date)); + } + public function scopeCreatesIn($query, $days) + { + return $query->whereDate('tasks.created_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeUpdatesBefore($query, $date) + { + return $query->whereDate('tasks.updated_at', '<=', Carbon::parse($date)); + } + public function scopeUpdatesAfter($query, $date) + { + return $query->whereDate('tasks.updated_at', '>=', Carbon::parse($date)); + } + public function scopeUpdatesIn($query, $days) + { + return $query->whereDate('tasks.updated_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeSpentFrom($query, $minute) + { + return $query->where('tasks.spent_time', '>=', $minute); + } + public function scopeSpentTo($query, $minute) + { + return $query->where('tasks.spent_time', '<=', $minute); + } + public function scopeEstimatedFrom($query, $minute) + { + return $query->where('tasks.estimated_time', '>=', $minute); + } + public function scopeEstimatedTo($query, $minute) + { + return $query->where('tasks.estimated_time', '<=', $minute); + } + public function scopeStartsBefore($query, $date) + { + return $query->whereDate('tasks.work_start', '<=', Carbon::parse($date)); + } + public function scopeStartsAfter($query, $date) + { + return $query->whereDate('tasks.work_start', '>=', Carbon::parse($date)); + } + public function scopeStartsIn($query, $days) + { + return $query->whereDate('tasks.work_start', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeFinishBefore($query, $date) + { + return $query->whereDate('tasks.work_finish', '<=', Carbon::parse($date)); + } + public function scopeFinishAfter($query, $date) + { + return $query->whereDate('tasks.work_finish', '>=', Carbon::parse($date)); + } + public function scopeFinishIn($query, $days) + { + return $query->whereDate('tasks.work_finish', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeCompletesBefore($query, $date) + { + return $query->where('tasks.completed_at', '<=', $date); + } + public function scopeCompletesAfter($query, $date) + { + return $query->where('tasks.completed_at', '>=', $date); + } + public function scopeCompletesIn($query, $days) + { + return $query->whereDate('tasks.completed_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeDueDateBefore($query, $date) + { + return $query->where('tasks.due_date', '<=', $date); + } + public function scopeDueDateAfter($query, $date) + { + return $query->where('tasks.due_date', '>=', $date); + } + public function scopeDueDateIn($query, $days) + { + return $query->whereDate('tasks.due_date', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + public function scopeOverSpentFrom($query, $minute) + { + return $query->whereColumn('spent_time', '>', 'estimated_time') + ->having('over_spent', '>=', $minute); + } + public function scopeOverSpentTo($query, $minute) + { + return $query->whereColumn('spent_time', '>', 'estimated_time') + ->having('over_spent', '<=', $minute); + } + public function scopeMyWatching($query) + { + return $query->whereJsonContains('watchers', auth()->id()); + } + public function scopeOverDue($query) + { + return $query->whereColumn('due_date', '<', 'completed_at'); + } + public function scopeReport($query, $group_field) + { + return $query->select(DB::raw($group_field.' , status_id, COUNT(*) as total, SUM(spent_time) as spent_sum, + SUM(estimated_time) as estimated_sum,SUM(ready_to_test) as test_sum, + COUNT(completed_at) as completed_total, SUM(on_time) as on_time_total, + SUM(Case When spent_time > estimated_time Then (spent_time - estimated_time) Else 0 End) as over_spent_sum, + SUM(Case When (spent_time <= estimated_time) And (completed_at) Then (estimated_time - spent_time) Else 0 End) + as under_spent_sum')) + ->groupBy($group_field, 'status_id'); + } +} diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php new file mode 100644 index 0000000..cafb084 --- /dev/null +++ b/app/Models/Transaction.php @@ -0,0 +1,164 @@ + 'array', + 'succeeded' => 'boolean', + ]; + + protected $fillable_relations = [ + 'user', 'business' + ]; + + protected $reportable = [ + 'user_id', 'business_id', 'amount', 'succeeded', 'options', // fields + ]; + + public $perPage = 12; + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => null, + 'sprint_id' => null, + 'system_id' => null, + 'user_id' => $this->user_id, + 'workflow_id' => null, + 'status_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function rules(): array + { + return [ + 'user_id' => 'required|', + 'business_id' => 'required', + 'amount' => 'required|integer|min:1', + ]; + } + + public function updateRelations() + { + // user relations + if (!empty($this->filled_relations['user'])) { + $this->dirties['user'] = $this->user_id; + } + + // business relations + if (!empty($this->filled_relations['business'])) { + $this->dirties['business'] = $this->business_id; + } + } + + public function reportActivity() + { + + } + + public function user() + { + return $this->belongsTo(User::class, 'user_id','id',__FUNCTION__); + } + + public function business() + { + return $this->belongsTo(Business::class, 'business_id','id',__FUNCTION__); + } + + /** + * Receive the authority key from the payment gateway + */ + public function prepare(): Transaction + { + $results = Zarinpal::request( + config('services.zarinpal.callback-url'), + $this->amount, + config('services.zarinpal.description') + ); + + $this->options = $results; + $this->save(); + + return $this; + } + + /** + * Redirect to the payment gateway + */ + public function redirect() + { + return Zarinpal::redirect(); + } + + public function verify(): Transaction + { + $results = Zarinpal::verify($this->amount, $this->options['Authority']); + if ($results['Status'] == 'verified_before') { + throw new \Exception("تراکنش قبلا تایید شده است."); + } + + if ($results['Status'] == 'success') { + $this->succeeded = true; + } else { + $this->succeeded = false; + } + + $options = array_merge($this->options, $results); + $this->options = $options; + $this->save(); + + return $this; + } + + /** + * Find a transaction via the authoriry key that psp provides us + * + * @throw ModelNotFound + */ + public static function findByAuthority(string $authority): Transaction + { + return static::where('options->Authority','=',$authority)->firstOrFail(); + } + + public function isWentToPaymentGateway(): bool + { + return !empty($this->options); + } + + public function hasBeenAppliedToWallet(): bool + { + return Arr::get($this->options,"applied", false); + } + + public function amountWasAppliedToWallet() + { + $options = $this->options; + $options['applied'] = true; + $this->options = $options; + + $this->save(); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 804799b..3479edf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,42 +2,194 @@ namespace App\Models; -use Illuminate\Contracts\Auth\MustVerifyEmail; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Foundation\Auth\User as Authenticatable; +use App\Models\File; +use App\Models\Model; +use App\Models\SoftDeletes; +use Illuminate\Validation\Rule; +use Illuminate\Http\UploadedFile; +use Spatie\MediaLibrary\HasMedia; +use Illuminate\Auth\Authenticatable; use Illuminate\Notifications\Notifiable; +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; -class User extends Authenticatable +class User extends Model implements AuthenticatableContract, AuthorizableContract, HasMedia { - use HasFactory, Notifiable; + use Notifiable, + SoftDeletes, + Authorizable, + Authenticatable, + InteractsWithMedia; - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'name', - 'email', - 'password', + const CONVERSION_NAME = 'avatar'; + + const COLLECTION_NAME = 'avatars'; + + public $casts = [ + 'has_avatar' => 'boolean', ]; - /** - * The attributes that should be hidden for arrays. - * - * @var array - */ - protected $hidden = [ - 'password', - 'remember_token', + protected $fillable = ['name', 'email','mobile', 'username','password','active','has_avatar']; + + protected $fillable_relations = ['projects']; + + protected $reportable = [ + 'name', 'username', 'mobile', 'email', // fields + ['projects' => 'project_user'] ]; + public $detach_relation = false; + /** - * The attributes that should be cast to native types. + * Specifies the user's FCM token * - * @var array + * @return string */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; + public function routeNotificationForFcm() + { + return $this->fingerprints->whereNotNull('fcm_token')->pluck('fcm_token')->all(); + } + + public function updateRelations() + { + // projects relations + if (!empty($this->filled_relations['projects']) || $this->detach_relation) { + $this->dirties['projects'] = $this->projects()->sync($this->filled_relations['projects'], $this->detach_relation); + } + } + + /** =============================== Validations ======================== */ + public function rules() + { + return [ + 'name' => 'required|string|max:225|min:2', + 'username' => ['required', Rule::unique('users', 'username')->ignore($this->id)], + 'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($this->id)], + 'password' => ['required','string','min:8'] + ]; + } + /** =============================== End Validations ==================== */ + + + /** =============================== Relations ========================== */ + public function fingerprints() + { + return $this->hasMany(Fingerprint::class, 'user_id', 'id'); + } + + public function businesses() + { + return $this->belongsToMany( + Business::class, + 'business_user', + 'user_id', + 'business_id', + 'id', + 'id', + __FUNCTION__ + ); + } + + + public function tasks() + { + return $this->hasMany(Task::class, 'user_id', 'id'); + } + + public function projects() + { + return $this->belongsToMany( + Project::class, + 'project_user', + 'user_id', + 'project_id', + 'id', + 'id', + __FUNCTION__ + )->using(ReportableRelation::class); + } + + public function files() + { + return $this->hasMany(File::class, 'user_id','id'); + } + + + /** =============================== End Relations ====================== */ + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => request('_business_info')['id'] ?? null, + 'user_id' => $this->id, + 'workflow_id' => null, + 'project_id' => null, + 'sprint_id' => null, + 'system_id' => null, + 'status_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function registerMediaCollections(): void + { + $this->addMediaCollection(static::COLLECTION_NAME) + ->acceptsMimeTypes([ + 'image/jpeg', + 'image/png', + 'image/tiff', + 'image/gif', + ]) + ->useDisk('public') + ->singleFile(); + } + + public function registerMediaConversions(Media $media = null): void + { + $this->addMediaConversion(static::CONVERSION_NAME) + ->width(200) + ->height(200) + ->queued() + ->nonOptimized() + ->performOnCollections(static::COLLECTION_NAME); + } + + + public function saveAsAvatar(UploadedFile $avatar): void + { + $this->addMedia($avatar)->toMediaCollection(static::COLLECTION_NAME); + $this->update([ + 'has_avatar' => true, + ]); + @unlink($this->getFirstMedia(static::COLLECTION_NAME)->getPath()); + } + + public function deleteAvatar(): void + { + $path = $this->getFirstMedia(static::COLLECTION_NAME)->getPath(); + $this->getFirstMedia(static::COLLECTION_NAME)->delete(); + $this->update([ + 'has_avatar' => false, + ]); + @unlink($path); + } + + public function getAvatarUrl(): ?string + { + if ($url = $this->getFirstMediaUrl(static::COLLECTION_NAME, static::CONVERSION_NAME)) { + return $url; + } + + return null; + } } diff --git a/app/Models/Work.php b/app/Models/Work.php new file mode 100644 index 0000000..0b6ce81 --- /dev/null +++ b/app/Models/Work.php @@ -0,0 +1,126 @@ + 'datetime:Y-m-d H:i', + 'ended_at' => 'datetime:Y-m-d H:i' + ]; + + protected $reportable = [ + 'message', 'minute_sum', 'started_at', 'ended_at', + ]; + + public function rules() + { + $started_at = request()->started_at ?? $this->started_at->format('Y-m-d H:i'); + $ended_at = request()->ended_at ?? $this->ended_at->format('Y-m-d H:i'); + return [ + 'message' => 'nullable|string|min:3|max:225', + 'started_at' => 'required|date_format:Y-m-d H:i|after:'.$this->task()->first()->created_at, + 'ended_at' => [ + 'required', 'date_format:Y-m-d H:i', 'after:'.$started_at, + function ($attribute, $value, $fail) use ($ended_at, $started_at) { + $works = $this::where([ + ['ended_at', '>', $started_at], + ['ended_at', '<', $ended_at], + ])->orWhere([ + ['started_at', '>', $started_at], + ['started_at', '<', $ended_at], + ])->orWhere([ + ['started_at', '>=', $started_at], + ['ended_at', '<=', $ended_at], + ])->when(isset($this->id), function ($query) { + return $query->where('id', '!=', $this->id); + })->exists(); + if ($works) { + $fail('The selected work is invalid.'); + } + } + ], + ]; + + } + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => $this->project_id, + 'sprint_id' => null, + 'workflow_id' => null, + 'status_id' => null, + 'system_id' => null, + 'task_id' => $this->task->id, + 'subject_id' => $this->id, + 'user_id' => null, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function updateRelations() + { + $this->filled_relations['task']['work_start'] = Work::where('task_id', $this->task_id)->orderBy('started_at')->first()->started_at ?? null; + $this->task()->update($this->filled_relations['task']); + } + + public function task() + { + return $this->belongsTo(Task::class, 'task_id', 'id'); + } + + public function scopeStartedAtIn($query, $days) + { + return $query->whereDate('started_at', '>=', Carbon::now()->modify('-'.$days.' day')->toDate()); + } + + public function scopeStartedAt($query, $date) + { + return $query->whereDate('started_at', '=', $date); + } + + public function scopeSpentTimeFrom($query, $data) + { + return $query->where('minute_sum', '>=', $data); + } + + public function scopeSpentTimeTo($query, $data) + { + return $query->where('minute_sum', '<=', $data); + } + + public function scopeReport($query) + { + return $query->select( + DB::raw('user_id , MIN(started_at) as started_at_min, MAX(ended_at) as ended_at_max, SUM(minute_sum) as work_minute_sum') + )->groupBy('user_id'); + } + + public function scopeReportByDate($query) + { + return $query->select( + DB::raw('DATE(started_at) as date, SUM(minute_sum) as work_minute_sum') + )->groupBy('date'); + } +} diff --git a/app/Models/Workflow.php b/app/Models/Workflow.php new file mode 100644 index 0000000..3989836 --- /dev/null +++ b/app/Models/Workflow.php @@ -0,0 +1,89 @@ + 'required|string|min:3|max:225', + 'desc' => 'nullable|string|min:3|max:225', + 'statuses' => 'required|array|min:2', + 'statuses.*' => 'required|array|min:2', + 'statuses.*.id' => 'nullable|numeric', + 'statuses.*.name' => 'required|string|min:3', + 'statuses.*.state' => 'required|numeric|between:0,3', + 'statuses.*.order' => 'nullable|numeric', + ]; + } + + public function getValueOf(?string $key) + { + $values = [ + 'business_id' => $this->business_id, + 'project_id' => null, + 'sprint_id' => null, + 'workflow_id' => $this->id, + 'status_id' => null, + 'system_id' => null, + 'user_id' => null, + 'task_id' => null, + 'subject_id' => $this->id, + ]; + + if ($key && isset($values, $key)) { + return $values[$key]; + } + + return $values; + } + + public function updateRelations() + { + $old_statuses_name = isset(\request('_business_info')['workflows'][$this->id]['statuses']) ? + array_keys(collect(\request('_business_info')['workflows'][$this->id]['statuses'])->toArray()) : + []; + $new_statuses_name = array_keys(collect($this->filled_relations['statuses'])->keyBy('id')->toArray()); + $removed_statuses_name = array_diff(array_merge($old_statuses_name, $new_statuses_name), $new_statuses_name); + + foreach ($removed_statuses_name as $status_name) { + //delete all statuses that removed name's from request->statuses + $this->statuses()->where('id', $status_name)->first()->delete(); + } + + foreach (request('statuses') as $status) { + //sync another statuses + $this->statuses() + ->updateOrCreate( + ['id' => $status['id'] ?? null, 'business_id' => $this->business_id, 'workflow_id' => $this->id], + ['name' => $status['name'], 'state' => $status['state'], 'order' => $status['order']] + ); + } + } + + public function business() + { + return $this->belongsTo(Business::class, 'business_id'); + } + + public function statuses() + { + return $this->hasMany(Status::class, 'workflow_id', 'id'); + } + + public function tasks() + { + return $this->hasMany(Task::class, 'workflow_id', 'id'); + } +} diff --git a/app/Notifications/DBNotification.php b/app/Notifications/DBNotification.php new file mode 100644 index 0000000..006b26a --- /dev/null +++ b/app/Notifications/DBNotification.php @@ -0,0 +1,49 @@ +message = $message; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['database']; + } + + /** + * 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..d9f1556 --- /dev/null +++ b/app/Notifications/FcmNotification.php @@ -0,0 +1,51 @@ +message = $message; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['fcm']; + } + + /** + * Get the voice representation of the notification. + * + * @param mixed $notifiable + * @return FcmMessage + */ + public function toFcm($notifiable) + { + return (new FcmMessage()) + ->data([ + 'title' => $this->message['title'], + 'body' => $this->message['body'], + ]); + } + +} diff --git a/app/Notifications/MailNotification.php b/app/Notifications/MailNotification.php new file mode 100644 index 0000000..8736089 --- /dev/null +++ b/app/Notifications/MailNotification.php @@ -0,0 +1,78 @@ +connection = 'redis'; + $this->queue = 'mail-queue'; + $this->afterCommit = true; + $this->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) + ->greeting($this->message['greeting']) + ->line($this->message['body']) + ->subject($this->message['subject']) + ->action('Notification Action', url('/')); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ee8ca5b..2dae397 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,11 @@ namespace App\Providers; +use App\Channels\FcmChannel; +use Illuminate\Notifications\ChannelManager; +use Illuminate\Support\Facades\Notification; use Illuminate\Support\ServiceProvider; +use GuzzleHttp\Client as HttpClient; class AppServiceProvider extends ServiceProvider { @@ -13,7 +17,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 ce74491..69ff803 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,8 +2,10 @@ namespace App\Providers; +use App\Models\Fingerprint; +use App\Utilities\RequestMixin; +use App\Utilities\BusinessInfoRequestMixin; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { @@ -25,6 +27,22 @@ class AuthServiceProvider extends ServiceProvider { $this->registerPolicies(); - // + $this->app['request']->mixin(new RequestMixin); + $this->app['request']->mixin(new BusinessInfoRequestMixin); + + $this->app['auth']->viaRequest('token', function ($request) { + + if ($request->bearerToken() === null) { + return null; + } + + $fingerprint = Fingerprint::where([ + 'token' => $request->bearerToken(), + 'agent' => $request->getAgent(), + 'os' => $request->getOS(), + ])->firstOrFail(); + + return $fingerprint->user->setAttribute('token', $fingerprint->token); + }); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a9f10a6..cdd86b4 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,10 +2,24 @@ namespace App\Providers; +use App\Events\ProjectUserCreate; +use App\Events\TagCreate; +use App\Events\ModelSaved; +use App\Events\BusinessUpdate; +use App\Events\TaskCreate; +use App\Events\TaskUpdate; +use App\Listeners\NotifHandler; +use App\Listeners\ProjectUserCreateNotif; +use App\Listeners\TagCreateNotif; +use App\Events\BusinessUserCreate; +use App\Listeners\TaskCreateNotif; +use App\Listeners\TaskUpdateNotif; use Illuminate\Auth\Events\Registered; +use App\Listeners\ActivityRegistration; +use App\Listeners\BusinessUpdateListener; +use App\Listeners\BusinessUserCreateNotif; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { @@ -18,6 +32,28 @@ class EventServiceProvider extends ServiceProvider Registered::class => [ SendEmailVerificationNotification::class, ], + ModelSaved::class => [ + ActivityRegistration::class, + NotifHandler::class, + ], + TagCreate::class => [ + TagCreateNotif::class, + ], + BusinessUserCreate::class => [ + BusinessUserCreateNotif::class, + ], + ProjectUserCreate::class => [ + ProjectUserCreateNotif::class, + ], + BusinessUpdate::class => [ + BusinessUpdateListener::class, + ], + TaskCreate::class => [ + TaskCreateNotif::class, + ], + TaskUpdate::class => [ + TaskUpdateNotif::class, + ], ]; /** diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3bd3c81..927be5d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -26,7 +26,7 @@ class RouteServiceProvider extends ServiceProvider * * @var string|null */ - // protected $namespace = 'App\\Http\\Controllers'; + protected $namespace = 'App\\Http\\Controllers'; /** * Define your route model bindings, pattern filters, etc. diff --git a/app/Rules/MaxBound.php b/app/Rules/MaxBound.php new file mode 100644 index 0000000..b4a530e --- /dev/null +++ b/app/Rules/MaxBound.php @@ -0,0 +1,40 @@ +bound; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return 'The :attribute is out of bound.'; + } +} diff --git a/app/Utilities/Avatar/DefaultConversionFileNamer.php b/app/Utilities/Avatar/DefaultConversionFileNamer.php new file mode 100644 index 0000000..2e11692 --- /dev/null +++ b/app/Utilities/Avatar/DefaultConversionFileNamer.php @@ -0,0 +1,15 @@ +model->getKey(); + } +} diff --git a/app/Utilities/Avatar/DefaultPathGenerator.php b/app/Utilities/Avatar/DefaultPathGenerator.php new file mode 100644 index 0000000..1b21029 --- /dev/null +++ b/app/Utilities/Avatar/DefaultPathGenerator.php @@ -0,0 +1,41 @@ +model->getTable()."/"; + } + + /* + * Get the path for the given media, relative to the root storage path. + */ + public function getPath(Media $media): string + { + return $this->getBasePath($media); + } + + /* + * Get the path for conversions of the given media, relative to the root storage path. + */ + public function getPathForConversions(Media $media): string + { + return $this->getBasePath($media); + } + + /* + * Get the path for responsive images of the given media, relative to the root storage path. + */ + public function getPathForResponsiveImages(Media $media): string + { + return $this->getBasePath($media).'/responsive-images/'; + } +} diff --git a/app/Utilities/BusinessInfoRequestMixin.php b/app/Utilities/BusinessInfoRequestMixin.php new file mode 100644 index 0000000..2fd1da3 --- /dev/null +++ b/app/Utilities/BusinessInfoRequestMixin.php @@ -0,0 +1,43 @@ +getFromBusinessInfo('info.users'); + }; + } + + public function isBusinessOwner() + { + return function(User $user) { + return Arr::get($this, "_business_info.info.users.{$user->id}.level") != enum('levels.owner.id'); + }; + } +} diff --git a/app/Utilities/Exceptions/Handler.php b/app/Utilities/Exceptions/Handler.php new file mode 100644 index 0000000..8d01e2b --- /dev/null +++ b/app/Utilities/Exceptions/Handler.php @@ -0,0 +1,58 @@ +getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + + $result = []; + foreach ($methods as $method) { + // invoke the method so we can collect the result of execution it + $result[$method->name] = $method->invoke($exception); + } + + // Clear the unnecessary method + unset($result['getTrace']); + unset($result['__toString']); + + // clear the null values then encode it as json + // so we can decode it as an object in the Monolog Processor + $result = json_encode(array_filter($result)); + + return Log::emergency($result); + } + + public function render($request, Throwable $exception) + { + return parent::render($request, $exception); + } +} diff --git a/app/Utilities/HelperClass/NotificationHelper.php b/app/Utilities/HelperClass/NotificationHelper.php new file mode 100644 index 0000000..4231cbe --- /dev/null +++ b/app/Utilities/HelperClass/NotificationHelper.php @@ -0,0 +1,72 @@ + $this->getMessageLine($payload, $route.'greeting'), + 'subject' => $this->getMessageLine($payload, $route.'subject'), + 'title' => $this->getMessageLine($payload, $route.'title'), + 'body' => $this->getMessageLine($payload, $route.'body', $options) + ]; + } + + /** + * Fetch message from notifications lang file + * + * @param $payload + * @param $key + * @param null $options + * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Translation\Translator|string|null + * + */ + public function getMessageLine($payload, $key, $options = []) + { + return __('notification.'.$payload->data->table_name.'.'.enum('cruds.inverse.'.$payload->data->crud_id.'.singular_name').'.'.$key, $options); + } + + /** + * Call notifications + * + * @param $users + * @param $notif + */ + public function sendNotifications($users, $notif) { +// switch ($level) { +// case "emergency": +//// Notification::send($users, new SmsNotification($notif)); +// case "critical": +// Notification::send($users, new MailNotification($notif)); +// case "high": +// Notification::send($users, new DBNotification($notif)); +// case "medium": +// Notification::send($users, new FcmNotification($notif)); +// case "low": +//// Notification::send($users, new SocketNotification($notif)); +// default: +//// Notification::send($users, new SocketNotification($notif)); +// } + Notification::send($users, new MailNotification($notif)); + Notification::send($users, new DBNotification($notif)); + Notification::send($users, new FcmNotification($notif)); + } + +} diff --git a/app/Utilities/Helpers/enum.php b/app/Utilities/Helpers/enum.php new file mode 100644 index 0000000..ad56050 --- /dev/null +++ b/app/Utilities/Helpers/enum.php @@ -0,0 +1,27 @@ +contains(".") ? "" : "."; + + // the first parameter of all enum keys are its filename + [$filename, $key] = explode(".", $key, 2); + + // because we do not want to load the file every time use require + $enums = require app_path("Enums/$filename.php"); + + // if the key that user provided not exists then null return + $enums = Arr::get($enums, $key, null); + + // if enum null means that key not found + throw_if($enums === null, 'Exception', "Undefined enum '{$key}'"); + + // if enum value is array its mean that user want to use it as collection + return is_array($enums) ? collect($enums) : $enums; + } +} diff --git a/app/Utilities/Helpers/http.php b/app/Utilities/Helpers/http.php new file mode 100644 index 0000000..bba7252 --- /dev/null +++ b/app/Utilities/Helpers/http.php @@ -0,0 +1 @@ +attach($piece->getFilename(), $piece); } } // if queue set then queue this command if ($queue !== null) { return dispatch(new AsyncCall(...func_get_args())); } try { // otherwise execute the pending request return $pendingRequest ->withToken($token) ->withoutRedirecting() ->withoutVerifying() ->$method($path, $data); } catch (Throwable $thr) { return $thr->response; } } } \ No newline at end of file diff --git a/app/Utilities/Helpers/index.php b/app/Utilities/Helpers/index.php new file mode 100644 index 0000000..64aade6 --- /dev/null +++ b/app/Utilities/Helpers/index.php @@ -0,0 +1 @@ +trim("."); if ($filename->isEmpty()) { continue; } require_once (string) $filename; } \ No newline at end of file diff --git a/app/Utilities/Helpers/permission.php b/app/Utilities/Helpers/permission.php new file mode 100644 index 0000000..a0e47ef --- /dev/null +++ b/app/Utilities/Helpers/permission.php @@ -0,0 +1,244 @@ +user()->id]['owner'] == true; + } +} + +/** + * Business Permit + */ + +if (!function_exists('businessAccess')) { + function businessAccess($ids) + { + return isset(request('_business_info')['info']['users'][$ids['user_id']?? auth()->id()]); + } +} + +if (!function_exists('isBusinessOwner')) { + function isBusinessOwner($ids) + { + return businessAccess($ids) && request('_business_info')['info']['users'][$ids['user_id'] ?? auth()->id()]['level'] == enum('levels.owner.id'); + } +} + +if (!function_exists('isAtLeastBusinessAdmin')) { + function isAtLeastBusinessAdmin($ids) + { + return businessAccess($ids) && request('_business_info')['info']['users'][$ids['user_id'] ?? auth()->id()]['level'] >= enum('levels.admin.id'); + } +} + +if (!function_exists('businessEdit')) { + function businessEdit($ids) + { + return isBusinessOwner($ids); + } +} + +if (!function_exists('businessUsers')) { + function businessUsers($ids) + { + return isBusinessOwner($ids); + } +} + +if (!function_exists('businessWorkFlows')) { + function businessWorkFlows($ids) + { + return isAtLeastBusinessAdmin($ids); + } +} + +if (!function_exists('businessProjects')) { + function businessProjects($ids) + { + return isBusinessOwner($ids); + } +} + +if (!function_exists('businessTags')) { + function businessTags($ids) + { + return isAtLeastBusinessAdmin($ids); + } +} + +if (!function_exists('businessStatuses')) { + function businessStatuses($ids) + { + return isAtLeastBusinessAdmin($ids); + } +} + +/** + * Project Permit + */ + +if (!function_exists('isInProject')) { + function isInProject($ids) + { + return isset(request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]); + } +} + +if (!function_exists('isActiveInProject')) { + function isActiveInProject($ids) + { + return request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] > 0; + } +} + +if (!function_exists('projectAccess')) { + function projectAccess($ids) + { + return isInProject($ids) && isActiveInProject($ids); + } +} + +if (!function_exists('isOwnerLevelInProject')) { + function isOwnerLevelInProject($ids) + { + return request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] == enum('levels.owner.id'); + } +} + +if (!function_exists('isAtLeastAdminLevelInProject')) { + function isAtLeastAdminLevelInProject($ids) + { + return request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] >= enum('levels.admin.id'); + } +} + +if (!function_exists('isAtLeastColleagueLevelInProject')) { + function isAtLeastColleagueLevelInProject($ids) + { + return request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] >= enum('levels.colleague.id'); + } +} + +if (!function_exists('isAtLeastGuestLevelInProject')) { + function isAtLeastGuestLevelInProject($ids) + { + return request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] >= enum('levels.guest.id'); + } +} + +if (!function_exists('isDefiniteGuestInProject')) { + function isDefiniteGuestInProject($ids) + { + return isInProject($ids) && request('_business_info')['info']['projects'][$ids['project_id']]['members'][$ids['user_id'] ?? auth()->id()]['level'] == enum('levels.guest.id'); + } +} + +if (!function_exists('isProjectOwner')) { + function isProjectOwner($ids) + { + return isInProject($ids) && isOwnerLevelInProject($ids); + } +} + +if (!function_exists('isProjectAdmin')) { + function isProjectAdmin($ids) + { + return isInProject($ids) && isAtLeastAdminLevelInProject($ids); + } +} + +if (!function_exists('isProjectColleague')) { + function isProjectColleague($ids) + { + return isInProject($ids) && isAtLeastColleagueLevelInProject($ids); + } +} + +if (!function_exists('isProjectGuest')) { + function isProjectGuest($ids) + { + return isInProject($ids) && isAtLeastGuestLevelInProject($ids); + } +} + +if (!function_exists('projectEdit')) { + function projectEdit($ids) + { + return isProjectAdmin($ids); + } +} + +if (!function_exists('projectUsers')) { + function projectUsers($ids) + { + return isProjectOwner($ids); + } +} + +if (!function_exists('projectSystems')) { + function projectSystems($ids) + { + return isProjectAdmin($ids); + } +} + +if (!function_exists('projectTasks')) { + function projectTasks($ids) + { + return isProjectAdmin($ids); + } +} + +if (!function_exists('projectSprints')) { + function projectSprints($ids) + { + return isProjectAdmin($ids); + } +} + +/** + * other + */ + +if (!function_exists('isActiveUser')) { + function isActiveUser($ids) + { + return businessAccess($ids) && request('_business_info')['info']['users'][$ids['user_id'] ?? auth()->id()]['level'] > enum('levels.inactive.id'); + } +} + diff --git a/app/Utilities/Jobs/AsyncCall.php b/app/Utilities/Jobs/AsyncCall.php new file mode 100644 index 0000000..6f71a01 --- /dev/null +++ b/app/Utilities/Jobs/AsyncCall.php @@ -0,0 +1,41 @@ +method = $method; + $this->service = $service; + $this->path = $path; + $this->data = $data; + $this->onQueue($queue); + $this->onConnection('database'); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + call($this->method, $this->service, $this->path, $this->data); + } +} diff --git a/app/Utilities/Logger/CreateCustomLogger.php b/app/Utilities/Logger/CreateCustomLogger.php new file mode 100644 index 0000000..ab07317 --- /dev/null +++ b/app/Utilities/Logger/CreateCustomLogger.php @@ -0,0 +1,83 @@ + Monolog::DEBUG, + 'info' => Monolog::INFO, + 'notice' => Monolog::NOTICE, + 'warning' => Monolog::WARNING, + 'error' => Monolog::ERROR, + 'critical' => Monolog::CRITICAL, + 'alert' => Monolog::ALERT, + 'emergency' => Monolog::EMERGENCY, + ]; + + public function __invoke(array $config) + { + if (!is_a($config['handler'], HandlerInterface::class, true)) { + throw new InvalidArgumentException( + $config['handler'] . ' must be an instance of ' . HandlerInterface::class + ); + } + + $with = array_merge( + ['level' => $this->level($config)], + $config['with'] ?? [], + $config['handler_with'] ?? [] + ); + + $handlers = [ + $this->prepareHandler(app()->make($config['handler'], $with), $config) + ]; + + $processors = [ + new LogServiceRecordProcessor, + ]; + + return new Monolog('custom', $handlers, $processors, new DateTimeZone('Asia/Tehran')); + } + + protected function getFallbackChannelName() + { + return app()->bound('env') ? app()->environment() : 'production'; + } + + protected function prepareHandler(HandlerInterface $handler, array $config = []) + { + $isHandlerFormattable = false; + + if (Monolog::API === 1) { + $isHandlerFormattable = true; + } elseif (Monolog::API === 2 && $handler instanceof FormattableHandlerInterface) { + $isHandlerFormattable = true; + } + + if ($isHandlerFormattable && !isset($config['formatter'])) { + $handler->setFormatter($this->formatter()); + } elseif ($isHandlerFormattable && $config['formatter'] !== 'default') { + $handler->setFormatter(app()->make($config['formatter'], $config['formatter_with'] ?? [])); + } + + return $handler; + } + + protected function level(array $config) + { + $level = $config['level'] ?? 'debug'; + + if (isset($this->levels[$level])) { + return $this->levels[$level]; + } + + throw new InvalidArgumentException('Invalid log level.'); + } +} diff --git a/app/Utilities/Logger/LogServiceRecordProcessor.php b/app/Utilities/Logger/LogServiceRecordProcessor.php new file mode 100644 index 0000000..202cd16 --- /dev/null +++ b/app/Utilities/Logger/LogServiceRecordProcessor.php @@ -0,0 +1,37 @@ + Uuid::uuid4()->toString(), + 'breadcrumbs' => DB::getQueryLog(), + 'request' => app('request'), + 'message' => Arr::get($exception, 'getMessage', $record['message']), + 'from' => env('CONTAINER_NAME'), + 'trace' => Arr::get($exception, 'getTraceAsString'), + 'name' => Arr::get($exception, 'getName'), + 'code' => Arr::get($exception, 'getCode'), + 'file' => Arr::get($exception, 'getFile'), + 'line' => Arr::get($exception, 'getLine'), + 'user_id' => Auth::hasResolvedGuards() ? Auth::id() : null, + 'created_at' => Carbon::now(), + ]); + } catch (Throwable $exception) { + return $record; + } + } +} diff --git a/app/Utilities/Middlewares/BindBusinessInfo.php b/app/Utilities/Middlewares/BindBusinessInfo.php new file mode 100644 index 0000000..788a54d --- /dev/null +++ b/app/Utilities/Middlewares/BindBusinessInfo.php @@ -0,0 +1,19 @@ +has('business_id') ? $request->business_id : $request->route('business'); + $businessInfo = Business::info($business_id); + $request->merge(['_business_info' => $businessInfo]); + + return $next($request); + } +} diff --git a/app/Utilities/Payload.php b/app/Utilities/Payload.php new file mode 100644 index 0000000..a845b28 --- /dev/null +++ b/app/Utilities/Payload.php @@ -0,0 +1,83 @@ +payload = $payload; + } + + public function getActor(): ?Notifiable + { + return new Notifiable(Arr::get($this->payload, "info.users." . $this['auth'])); + } + + public function getSubject(): ?Notifiable + { + return new Notifiable( + Arr::get($this->payload, "info.users." . $this['data']['original']['user_id']) + ); + } + + public function getOwners(): Collection + { + return new Collection( + Arr::where($this->payload['info']['users'], fn ($user) => $user['level'] === 4) + ); + } + + public function getTableName(): ?Stringable + { + return Str::of(Arr::get($this->payload, "data.table_name")); + } + + public function getActId() + { + return Arr::get($this->payload, "data.crud_id"); + } + + public function getTitle() + { + return Arr::get($this->payload, "info.name"); + } + + public function isPivot(): bool + { + return $this->getTableName()->contains("_"); + } + + public function offsetExists($key) + { + return isset($this->payload[$key]); + } + + public function offsetGet($key) + { + return $this->payload[$key]; + } + + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->payload[] = $value; + } else { + $this->payload[$key] = $value; + } + } + + public function offsetUnset($key) + { + unset($this->payload[$key]); + } +} diff --git a/app/Utilities/Providers/AuthServiceProvider.php b/app/Utilities/Providers/AuthServiceProvider.php new file mode 100644 index 0000000..4b25477 --- /dev/null +++ b/app/Utilities/Providers/AuthServiceProvider.php @@ -0,0 +1,54 @@ +app['auth']->viaRequest('api', function (Request $request) { + // with token + if ($request->bearerToken()) { + return $this->loginUserWithToken($request); + } + + // first party + if ($request->getHost() === $container_name = getenv('CONTAINER_NAME')) { + return $this->loginServiceWithSetUser($container_name); + } + + // without token + return null; + }); + } + + public function loginUserWithToken(Request $request): User + { + $attributes = Http::retry(3, 100) + ->withToken($request->bearerToken()) + ->get(env('USER_URL') . 'auth') + ->json(); + + $attributes = Arr::get($attributes, 'data',[]); + +// dd($attributes); + return new User($attributes); + } + + public function loginServiceWithSetUser(string $container_name): User + { + Auth::setUser($user = new User([ + 'name' => $container_name, + 'is_service' => true, + ])); + + return $user; + } +} diff --git a/app/Utilities/Providers/HiLibraryServiceProvider.php b/app/Utilities/Providers/HiLibraryServiceProvider.php new file mode 100644 index 0000000..7ac7c8e --- /dev/null +++ b/app/Utilities/Providers/HiLibraryServiceProvider.php @@ -0,0 +1,49 @@ +app->singleton( + \Illuminate\Contracts\Debug\ExceptionHandler::class, + \App\HiLib\Exceptions\Handler::class + ); + + Stringable::macro('then', function($callback) { + return new Stringable($callback($this)); + }); + } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + DB::enableQueryLog(); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + // + } +} diff --git a/app/Utilities/RequestMixin.php b/app/Utilities/RequestMixin.php new file mode 100644 index 0000000..c53192e --- /dev/null +++ b/app/Utilities/RequestMixin.php @@ -0,0 +1,51 @@ + $this->hasHeader('USER_AGENT') ? (new Agent())->platform() : null; + } + + /** + * Return the browser + * + * @return Closure + */ + public function getAgent() + { + return fn() => $this->hasHeader('USER_AGENT') ? (new Agent())->browser() : null; + } + + + /** + * Get the user location's based on her/his IP address + * + * @return Closure + */ + public function getLocation() + { + return fn() => geoip()->getLocation($this->getClientIp()); + } + + public function getCurrentToken() + { + // todo: how to implement ip lookup for current token + return fn() => Auth::user()->token; + } +} diff --git a/app/Utilities/Zarinpal/Drivers/DriverInterface.php b/app/Utilities/Zarinpal/Drivers/DriverInterface.php new file mode 100644 index 0000000..8227518 --- /dev/null +++ b/app/Utilities/Zarinpal/Drivers/DriverInterface.php @@ -0,0 +1,46 @@ +restCall('PaymentRequest.json', $inputs); + + if ($result['Status'] == 100) { + return ['Authority' => $result['Authority']]; + } else { + return ['error' => $result['Status']]; + } + } + + /** + * requestWithExtra driver. + * + * @param $inputs + * + * @return array + */ + public function requestWithExtra($inputs) + { + $result = $this->restCall('PaymentRequestWithExtra.json', $inputs); + + if ($result['Status'] == 100) { + return ['Authority' => $result['Authority']]; + } else { + return [ + 'Status' => 'error', + 'error' => !empty($result['Status']) ? $result['Status'] : null, + 'errorInfo' => !empty($result['errors']) ? $result['errors'] : null, + ]; + } + } + + /** + * verify driver. + * + * @param $inputs + * + * @return array + */ + public function verify($inputs) + { + $result = $this->restCall('PaymentVerification.json', $inputs); + + if ($result['Status'] == 100) { + return [ + 'Status' => 'success', + 'RefID' => $result['RefID'], + ]; + } elseif ($result['Status'] == 101) { + return [ + 'Status' => 'verified_before', + 'RefID' => $result['RefID'], + ]; + } else { + return [ + 'Status' => 'error', + 'error' => !empty($result['Status']) ? $result['Status'] : null, + 'errorInfo' => !empty($result['errors']) ? $result['errors'] : null, + ]; + } + } + + /** + * verifyWithExtra driver. + * + * @param $inputs + * + * @return array + */ + public function verifyWithExtra($inputs) + { + $result = $this->restCall('PaymentVerificationWithExtra.json', $inputs); + + if ($result['Status'] == 100) { + return [ + 'Status' => 'success', + 'RefID' => $result['RefID'], + 'ExtraDetail' => $result['ExtraDetail'], + ]; + } elseif ($result['Status'] == 101) { + return [ + 'Status' => 'verified_before', + 'RefID' => $result['RefID'], + 'ExtraDetail' => $result['ExtraDetail'], + ]; + } else { + return [ + 'Status' => 'error', + 'error' => !empty($result['Status']) ? $result['Status'] : null, + 'errorInfo' => !empty($result['errors']) ? $result['errors'] : null, + ]; + } + } + + /** + * unverifiedTransactions driver. + * + * @param $inputs + * + * @return array + */ + public function unverifiedTransactions($inputs) + { + $result = $this->restCall('UnverifiedTransactions.json', $inputs); + + if ($result['Status'] == 100) { + return ['Status' => 'success', 'Authorities' => $result['Authorities']]; + } else { + return [ + 'Status' => 'error', + 'error' => !empty($result['Status']) ? $result['Status'] : null, + 'errorInfo' => !empty($result['errors']) ? $result['errors'] : null, + ]; + } + } + + /** + * refreshAuthority driver. + * + * @param $inputs + * + * @return array + */ + public function refreshAuthority($inputs) + { + $result = $this->restCall('RefreshAuthority.json', $inputs); + + if ($result['Status'] == 100) { + return ['Status' => 'success', 'refreshed' => true]; + } else { + return ['Status' => 'error', 'error' => $result['Status']]; + } + } + + /** + * request rest and return the response. + * + * @param $uri + * @param $data + * + * @return mixed + */ + private function restCall($uri, $data) + { + try { + $client = new Client(['base_uri' => $this->baseUrl]); + $response = $client->request('POST', $uri, ['json' => $data]); + + $rawBody = $response->getBody()->getContents(); + $body = json_decode($rawBody, true); + } catch (RequestException $e) { + $response = $e->getResponse(); + $rawBody = is_null($response) ? '{"Status":-98,"message":"http connection error"}' : $response->getBody()->getContents(); + $body = json_decode($rawBody, true); + } + + if (!isset($result['Status'])) { + $result['Status'] = -99; + } + + return $body; + } + + /** + * @param mixed $baseUrl + * + * @return void + */ + public function setAddress($baseUrl) + { + $this->baseUrl = $baseUrl; + } + + public function enableSandbox() + { + $this->setAddress('https://sandbox.zarinpal.com/pg/rest/WebGate/'); + } +} diff --git a/app/Utilities/Zarinpal/Laravel/Facade/Zarinpal.php b/app/Utilities/Zarinpal/Laravel/Facade/Zarinpal.php new file mode 100644 index 0000000..921c2fb --- /dev/null +++ b/app/Utilities/Zarinpal/Laravel/Facade/Zarinpal.php @@ -0,0 +1,24 @@ +app->singleton(DriverInterface::class, function () { + return new RestDriver(); + }); + + $this->app->singleton('Zarinpal', function () { + $merchantID = config('services.zarinpal.merchantID', config('Zarinpal.merchantID', 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX')); + + $zarinpal = new Zarinpal($merchantID, $this->app->make(DriverInterface::class)); + + if (config('services.zarinpal.sandbox', false)) { + $zarinpal->enableSandbox(); + } + if (config('services.zarinpal.zarinGate', false)) { + $zarinpal->isZarinGate(); + } + + return $zarinpal; + }); + } + + /** + * Publish the plugin configuration. + */ + public function boot() + { + // + } +} diff --git a/app/Utilities/Zarinpal/Zarinpal.php b/app/Utilities/Zarinpal/Zarinpal.php new file mode 100644 index 0000000..6339b33 --- /dev/null +++ b/app/Utilities/Zarinpal/Zarinpal.php @@ -0,0 +1,130 @@ +merchantID = $merchantID; + $this->driver = $driver; + } + + /** + * send request for money to zarinpal + * and redirect if there was no error. + * + * @param string $callbackURL + * @param string $Amount + * @param string $Description + * @param string $Email + * @param string $Mobile + * @param null $additionalData + * + * @return array|@redirect + */ + public function request($callbackURL, $Amount, $Description, $Email = null, $Mobile = null, $additionalData = null) + { + $inputs = [ + 'MerchantID' => $this->merchantID, + 'CallbackURL' => $callbackURL, + 'Amount' => $Amount, + 'Description' => $Description, + ]; + if (!is_null($Email)) { + $inputs['Email'] = $Email; + } + if (!is_null($Mobile)) { + $inputs['Mobile'] = $Mobile; + } + if (!is_null($additionalData)) { + $inputs['AdditionalData'] = $additionalData; + $results = $this->driver->requestWithExtra($inputs); + } else { + $results = $this->driver->request($inputs); + } + + if (empty($results['Authority'])) { + $results['Authority'] = null; + } + $this->Authority = $results['Authority']; + + return $results; + } + + /** + * verify that the bill is paid or not + * by checking authority, amount and status. + * + * @param $amount + * @param $authority + * + * @return array + */ + public function verify($amount, $authority) + { + // backward compatibility + if (count(func_get_args()) == 3) { + $amount = func_get_arg(1); + $authority = func_get_arg(2); + } + + $inputs = [ + 'MerchantID' => $this->merchantID, + 'Authority' => $authority, + 'Amount' => $amount, + ]; + + return $this->driver->verifyWithExtra($inputs); + } + + public function redirect() + { + header('Location: ' . sprintf($this->redirectUrl, $this->Authority)); + die; + } + + /** + * @return string + */ + public function redirectUrl() + { + return sprintf($this->redirectUrl, $this->Authority); + } + + /** + * @return DriverInterface + */ + public function getDriver() + { + return $this->driver; + } + + /** + * active sandbox mod for test env. + */ + public function enableSandbox() + { + $this->redirectUrl = 'https://sandbox.zarinpal.com/pg/StartPay/%u'; + $this->getDriver()->enableSandbox(); + } + + /** + * active zarinGate mode. + */ + public function isZarinGate() + { + $this->redirectUrl = $this->redirectUrl . '/ZarinGate'; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 037e17d..fdcb8bd 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -52,4 +52,11 @@ $app->singleton( | */ +$app->router->group([ + 'namespace' => 'App\Http\Controllers', + 'prefix' => '/user/v1/' +], function ($router) { + require __DIR__.'/../routes/api.php'; +}); + return $app; diff --git a/composer.json b/composer.json index 3795a6d..6c87a76 100644 --- a/composer.json +++ b/composer.json @@ -8,12 +8,24 @@ ], "license": "MIT", "require": { + "ext-gd": "*", + "ext-json": "*", "php": "^7.3|^8.0", + "anik/amqp": "^1.3", + "torann/geoip": "^3.0", + "spatie/image": "^1.0", + "morilog/jalali": "3.*", + "laravel/tinker": "^2.5", "fideloper/proxy": "^4.4", - "fruitcake/laravel-cors": "^2.0", - "guzzlehttp/guzzle": "^7.0.1", + "jenssegers/agent": "^2.6", + "laravel/socialite": "^5.1", "laravel/framework": "^8.12", - "laravel/tinker": "^2.5" + "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", + "league/flysystem-cached-adapter": "~1.0" }, "require-dev": { "facade/ignition": "^2.5", @@ -21,7 +33,8 @@ "laravel/sail": "^1.0.1", "mockery/mockery": "^1.4.2", "nunomaduro/collision": "^5.0", - "phpunit/phpunit": "^9.3.3" + "phpunit/phpunit": "^9.3.3", + "andreaselia/laravel-api-to-postman":"^1.0" }, "config": { "optimize-autoloader": true, @@ -38,7 +51,14 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" - } + }, + "files": [ + "app/Utilities/Helpers/index.php" + ], + "classmap": [ + "database/seeds", + "database/factories" + ] }, "autoload-dev": { "psr-4": { diff --git a/composer.lock b/composer.lock index a5ae24e..89d7c77 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4a16c3c541bd99241cab1c21ce8e83ac", + "content-hash": "5fa03c0090cecfc0c45ad964962ea582", "packages": [ + { + "name": "anik/amqp", + "version": "v1.5", + "source": { + "type": "git", + "url": "https://github.com/ssi-anik/amqp.git", + "reference": "091620674aefc6bd5666be2fd9cbbdccfded93cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ssi-anik/amqp/zipball/091620674aefc6bd5666be2fd9cbbdccfded93cc", + "reference": "091620674aefc6bd5666be2fd9cbbdccfded93cc", + "shasum": "" + }, + "require": { + "illuminate/container": "^5.8|^6.0|^7.0|^8.0", + "illuminate/pipeline": "^5.8|^6.0|^7.0|^8.0", + "illuminate/support": "^5.8|^6.0|^7.0|^8.0", + "laravel/helpers": "^1.2", + "php": ">=7.0", + "php-amqplib/php-amqplib": "^2.9 !=2.12.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Anik\\Amqp\\ServiceProviders\\AmqpServiceProvider" + ], + "aliases": { + "Amqp": "Anik\\Amqp\\Facades\\Amqp" + } + } + }, + "autoload": { + "psr-4": { + "Anik\\Amqp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Syed Sirajul Islam Anik", + "email": "sirajul.islam.anik@gmail.com" + } + ], + "description": "php-amqplib wrapper that eases the consumption of RabbitMQ. A painless way of using RabbitMQ", + "support": { + "issues": "https://github.com/ssi-anik/amqp/issues", + "source": "https://github.com/ssi-anik/amqp/tree/v1.5" + }, + "time": "2020-09-25T00:37:54+00:00" + }, { "name": "asm89/stack-cors", "version": "v2.0.2", @@ -56,8 +111,167 @@ "cors", "stack" ], + "support": { + "issues": "https://github.com/asm89/stack-cors/issues", + "source": "https://github.com/asm89/stack-cors/tree/v2.0.2" + }, "time": "2020-10-29T16:03:21+00:00" }, + { + "name": "aws/aws-sdk-php", + "version": "3.173.23", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "259fa1a47a46f81e66b1ea328ed961a58fa061c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/259fa1a47a46f81e66b1ea328ed961a58fa061c2", + "reference": "259fa1a47a46f81e66b1ea328ed961a58fa061c2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.7.0", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.173.23" + }, + "time": "2021-03-05T19:20:35+00:00" + }, + { + "name": "beberlei/assert", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "5367e3895976b49704ae671f75bc5f0ba1b986ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/5367e3895976b49704ae671f75bc5f0ba1b986ab", + "reference": "5367e3895976b49704ae671f75bc5f0ba1b986ab", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Assert\\": "lib/Assert" + }, + "files": [ + "lib/Assert/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.0" + }, + "time": "2020-11-13T20:02:54+00:00" + }, { "name": "brick/math", "version": "0.9.2", @@ -102,6 +316,10 @@ "brick", "math" ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.2" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/brick/math", @@ -141,6 +359,10 @@ "MIT" ], "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, "time": "2019-12-04T15:06:13+00:00" }, { @@ -218,6 +440,10 @@ "uppercase", "words" ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.x" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -294,6 +520,10 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -359,6 +589,10 @@ "cron", "schedule" ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + }, "funding": [ { "url": "https://github.com/dragonmantank", @@ -423,6 +657,10 @@ "validation", "validator" ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + }, "funding": [ { "url": "https://github.com/egulias", @@ -483,6 +721,10 @@ "proxy", "trusted proxy" ], + "support": { + "issues": "https://github.com/fideloper/TrustedProxy/issues", + "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" + }, "time": "2020-10-22T13:48:01+00:00" }, { @@ -550,6 +792,10 @@ "crossdomain", "laravel" ], + "support": { + "issues": "https://github.com/fruitcake/laravel-cors/issues", + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.3" + }, "funding": [ { "url": "https://github.com/barryvdh", @@ -608,6 +854,10 @@ "Result-Type", "result" ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -698,6 +948,10 @@ "rest", "web service" ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.2.0" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -720,16 +974,16 @@ }, { "name": "guzzlehttp/promises", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "60d379c243457e073cff02bc323a2a86cb355631" + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", - "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", "shasum": "" }, "require": { @@ -767,7 +1021,11 @@ "keywords": [ "promise" ], - "time": "2020-09-30T07:37:28+00:00" + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.1" + }, + "time": "2021-03-07T09:25:29+00:00" }, { "name": "guzzlehttp/psr7", @@ -838,146 +1096,359 @@ "uri", "url" ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.7.0" + }, "time": "2020-09-30T07:37:11+00:00" }, { - "name": "laravel/framework", - "version": "v8.29.0", + "name": "intervention/image", + "version": "2.5.1", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e" + "url": "https://github.com/Intervention/image.git", + "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d2eba352b3b3a3c515b18c5726b373fe5026733e", - "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e", + "url": "https://api.github.com/repos/Intervention/image/zipball/abbf18d5ab8367f96b3205ca3c89fb2fa598c69e", + "reference": "abbf18d5ab8367f96b3205ca3c89fb2fa598c69e", "shasum": "" }, "require": { - "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^3.0.2", - "egulias/email-validator": "^2.1.10", - "ext-json": "*", - "ext-mbstring": "*", - "ext-openssl": "*", - "league/commonmark": "^1.3", - "league/flysystem": "^1.1", - "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.31", - "opis/closure": "^3.6", - "php": "^7.3|^8.0", - "psr/container": "^1.0", - "psr/simple-cache": "^1.0", - "ramsey/uuid": "^4.0", - "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^5.1.4", - "symfony/error-handler": "^5.1.4", - "symfony/finder": "^5.1.4", - "symfony/http-foundation": "^5.1.4", - "symfony/http-kernel": "^5.1.4", - "symfony/mime": "^5.1.4", - "symfony/process": "^5.1.4", - "symfony/routing": "^5.1.4", - "symfony/var-dumper": "^5.1.4", - "tijsverkoyen/css-to-inline-styles": "^2.2.2", - "vlucas/phpdotenv": "^5.2", - "voku/portable-ascii": "^1.4.8" - }, - "conflict": { - "tightenco/collect": "<5.5.33" - }, - "provide": { - "psr/container-implementation": "1.0" - }, - "replace": { - "illuminate/auth": "self.version", - "illuminate/broadcasting": "self.version", - "illuminate/bus": "self.version", - "illuminate/cache": "self.version", - "illuminate/collections": "self.version", - "illuminate/config": "self.version", - "illuminate/console": "self.version", - "illuminate/container": "self.version", - "illuminate/contracts": "self.version", - "illuminate/cookie": "self.version", - "illuminate/database": "self.version", - "illuminate/encryption": "self.version", - "illuminate/events": "self.version", - "illuminate/filesystem": "self.version", - "illuminate/hashing": "self.version", - "illuminate/http": "self.version", - "illuminate/log": "self.version", - "illuminate/macroable": "self.version", - "illuminate/mail": "self.version", - "illuminate/notifications": "self.version", - "illuminate/pagination": "self.version", - "illuminate/pipeline": "self.version", - "illuminate/queue": "self.version", - "illuminate/redis": "self.version", - "illuminate/routing": "self.version", - "illuminate/session": "self.version", - "illuminate/support": "self.version", - "illuminate/testing": "self.version", - "illuminate/translation": "self.version", - "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.4.0" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", - "doctrine/dbal": "^2.6|^3.0", - "filp/whoops": "^2.8", - "guzzlehttp/guzzle": "^6.5.5|^7.0.1", - "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "^1.4.2", - "orchestra/testbench-core": "^6.8", - "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.5.8|^9.3.3", - "predis/predis": "^1.1.1", - "symfony/cache": "^5.1.4" + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", - "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", - "ext-ftp": "Required to use the Flysystem FTP driver.", - "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", - "ext-memcached": "Required to use the memcache cache driver.", - "ext-pcntl": "Required to use all features of the queue worker.", - "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", - "filp/whoops": "Required for friendly error pages in development (^2.8).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", - "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", - "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (^1.4.2).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", - "predis/predis": "Required to use the predis connector (^1.1.2).", - "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", - "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "8.x-dev" + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } } }, "autoload": { - "files": [ - "src/Illuminate/Collections/helpers.php", - "src/Illuminate/Events/functions.php", - "src/Illuminate/Foundation/helpers.php", - "src/Illuminate/Support/helpers.php" - ], + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@olivervogel.com", + "homepage": "http://olivervogel.com/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/master" + }, + "time": "2019-11-02T09:15:47+00:00" + }, + { + "name": "jaybizzle/crawler-detect", + "version": "v1.2.105", + "source": { + "type": "git", + "url": "https://github.com/JayBizzle/Crawler-Detect.git", + "reference": "719c1ed49224857800c3dc40838b6b761d046105" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/719c1ed49224857800c3dc40838b6b761d046105", + "reference": "719c1ed49224857800c3dc40838b6b761d046105", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jaybizzle\\CrawlerDetect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Beech", + "email": "m@rkbee.ch", + "role": "Developer" + } + ], + "description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent", + "homepage": "https://github.com/JayBizzle/Crawler-Detect/", + "keywords": [ + "crawler", + "crawler detect", + "crawler detector", + "crawlerdetect", + "php crawler detect" + ], + "support": { + "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.105" + }, + "time": "2021-03-03T20:55:48+00:00" + }, + { + "name": "jenssegers/agent", + "version": "v2.6.4", + "source": { + "type": "git", + "url": "https://github.com/jenssegers/agent.git", + "reference": "daa11c43729510b3700bc34d414664966b03bffe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jenssegers/agent/zipball/daa11c43729510b3700bc34d414664966b03bffe", + "reference": "daa11c43729510b3700bc34d414664966b03bffe", + "shasum": "" + }, + "require": { + "jaybizzle/crawler-detect": "^1.2", + "mobiledetect/mobiledetectlib": "^2.7.6", + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5.0|^6.0|^7.0" + }, + "suggest": { + "illuminate/support": "Required for laravel service providers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + }, + "laravel": { + "providers": [ + "Jenssegers\\Agent\\AgentServiceProvider" + ], + "aliases": { + "Agent": "Jenssegers\\Agent\\Facades\\Agent" + } + } + }, + "autoload": { + "psr-4": { + "Jenssegers\\Agent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com" + } + ], + "description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect", + "homepage": "https://github.com/jenssegers/agent", + "keywords": [ + "Agent", + "browser", + "desktop", + "laravel", + "mobile", + "platform", + "user agent", + "useragent" + ], + "support": { + "issues": "https://github.com/jenssegers/agent/issues", + "source": "https://github.com/jenssegers/agent/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://github.com/jenssegers", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/jenssegers/agent", + "type": "tidelift" + } + ], + "time": "2020-06-13T08:05:20+00:00" + }, + { + "name": "laravel/framework", + "version": "v8.31.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "2aa5c2488d25178ebc097052c7897a0e463ddc35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/2aa5c2488d25178ebc097052c7897a0e463ddc35", + "reference": "2aa5c2488d25178ebc097052c7897a0e463ddc35", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "dragonmantank/cron-expression": "^3.0.2", + "egulias/email-validator": "^2.1.10", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "league/commonmark": "^1.3", + "league/flysystem": "^1.1", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.31", + "opis/closure": "^3.6", + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0", + "ramsey/uuid": "^4.0", + "swiftmailer/swiftmailer": "^6.0", + "symfony/console": "^5.1.4", + "symfony/error-handler": "^5.1.4", + "symfony/finder": "^5.1.4", + "symfony/http-foundation": "^5.1.4", + "symfony/http-kernel": "^5.1.4", + "symfony/mime": "^5.1.4", + "symfony/process": "^5.1.4", + "symfony/routing": "^5.1.4", + "symfony/var-dumper": "^5.1.4", + "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "vlucas/phpdotenv": "^5.2", + "voku/portable-ascii": "^1.4.8" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.155", + "doctrine/dbal": "^2.6|^3.0", + "filp/whoops": "^2.8", + "guzzlehttp/guzzle": "^6.5.5|^7.0.1", + "league/flysystem-cached-adapter": "^1.0", + "mockery/mockery": "^1.4.2", + "orchestra/testbench-core": "^6.8", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "predis/predis": "^1.1.1", + "symfony/cache": "^5.1.4" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.8).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "mockery/mockery": "Required to use mocking (^1.4.2).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", + "predis/predis": "Required to use the predis connector (^1.1.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", + "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], "psr-4": { "Illuminate\\": "src/Illuminate/", "Illuminate\\Support\\": [ @@ -992,228 +1463,1673 @@ ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-03-04T15:22:36+00:00" + }, + { + "name": "laravel/helpers", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/helpers.git", + "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/helpers/zipball/febb10d8daaf86123825de2cb87f789a3371f0ac", + "reference": "febb10d8daaf86123825de2cb87f789a3371f0ac", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0", + "php": "^7.1.3|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Dries Vints", + "email": "dries.vints@gmail.com" + } + ], + "description": "Provides backwards compatibility for helpers in the latest Laravel release.", + "keywords": [ + "helpers", + "laravel" + ], + "support": { + "source": "https://github.com/laravel/helpers/tree/v1.4.1" + }, + "time": "2021-02-16T15:27:11+00:00" + }, + { + "name": "laravel/legacy-factories", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/legacy-factories.git", + "reference": "5e3fe2fd5fda64e20ea5c74c831a7346294e902a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/legacy-factories/zipball/5e3fe2fd5fda64e20ea5c74c831a7346294e902a", + "reference": "5e3fe2fd5fda64e20ea5c74c831a7346294e902a", + "shasum": "" + }, + "require": { + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0", + "symfony/finder": "^3.4|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Illuminate\\Database\\Eloquent\\LegacyFactoryServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Database\\Eloquent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The legacy version of the Laravel Eloquent factories.", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2020-10-27T14:25:32+00:00" + }, + { + "name": "laravel/socialite", + "version": "v5.2.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "8d25d574b4f2005411c0b9cb527ef5e745c1b07d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/8d25d574b4f2005411c0b9cb527ef5e745c1b07d", + "reference": "8d25d574b4f2005411c0b9cb527ef5e745c1b07d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "illuminate/http": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "league/oauth1-client": "^1.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "illuminate/contracts": "^6.0|^7.0", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ], + "aliases": { + "Socialite": "Laravel\\Socialite\\Facades\\Socialite" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", + "homepage": "https://laravel.com", + "keywords": [ + "laravel", + "oauth" + ], + "support": { + "issues": "https://github.com/laravel/socialite/issues", + "source": "https://github.com/laravel/socialite" + }, + "time": "2021-03-02T16:50:47+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.6.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "04ad32c1a3328081097a181875733fa51f402083" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", + "reference": "04ad32c1a3328081097a181875733fa51f402083", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/contracts": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.10.4", + "symfony/var-dumper": "^4.3.4|^5.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpunit/phpunit": "^8.5.8|^9.3.3" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.6.1" + }, + "time": "2021-03-02T16:53:12+00:00" + }, + { + "name": "league/commonmark", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "scrutinizer/ocular": "1.7.*" + }, + "require-dev": { + "cebe/markdown": "~1.0", + "commonmark/commonmark.js": "0.29.2", + "erusev/parsedown": "~1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "~1.4", + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" + }, + "bin": [ + "bin/commonmark" + ], + "type": "library", + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", + "type": "custom" + }, + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2020-10-31T13:49:32+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.x" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-08-23T07:39:11+00:00" + }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.29", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "4e25cc0582a36a786c31115e419c6e40498f6972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4e25cc0582a36a786c31115e419c6e40498f6972", + "reference": "4e25cc0582a36a786c31115e419c6e40498f6972", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.20.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.29" + }, + "time": "2020-10-08T18:58:37+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "time": "2020-07-25T15:56:04+00:00" + }, + { + "name": "league/glide", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/glide.git", + "reference": "ae5e26700573cb678919d28e425a8b87bc71c546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/glide/zipball/ae5e26700573cb678919d28e425a8b87bc71c546", + "reference": "ae5e26700573cb678919d28e425a8b87bc71c546", + "shasum": "" + }, + "require": { + "intervention/image": "^2.4", + "league/flysystem": "^1.0", + "php": "^7.2|^8.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "phpunit/php-token-stream": "^3.1|^4.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Glide\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "homepage": "http://reinink.ca" + } + ], + "description": "Wonderfully easy on-demand image manipulation library with an HTTP based API.", + "homepage": "http://glide.thephpleague.com", + "keywords": [ + "ImageMagick", + "editing", + "gd", + "image", + "imagick", + "league", + "manipulation", + "processing" + ], + "support": { + "issues": "https://github.com/thephpleague/glide/issues", + "source": "https://github.com/thephpleague/glide/tree/1.7.0" + }, + "time": "2020-11-05T17:34:03+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-01-18T20:58:21+00:00" + }, + { + "name": "league/oauth1-client", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/1e7e6be2dc543bf466236fb171e5b20e1b06aee6", + "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "php": ">=7.1||>=8.0" + }, + "require-dev": { + "ext-simplexml": "*", + "friendsofphp/php-cs-fixer": "^2.17", + "mockery/mockery": "^1.3.3", + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5||9.5" + }, + "suggest": { + "ext-simplexml": "For decoding XML-based responses." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev", + "dev-develop": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth1-client/issues", + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.0" + }, + "time": "2021-01-20T01:40:53+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/master" + }, + "funding": [ + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "2.8.37", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "9841e3c46f5bd0739b53aed8ac677fa712943df7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/9841e3c46f5bd0739b53aed8ac677fa712943df7", + "reference": "9841e3c46f5bd0739b53aed8ac677fa712943df7", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35||~5.7" + }, + "type": "library", + "autoload": { + "classmap": [ + "Mobile_Detect.php" + ], + "psr-0": { + "Detection": "namespaced/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "keywords": [ + "detect mobile devices", + "mobile", + "mobile detect", + "mobile detector", + "php mobile detect" + ], + "support": { + "issues": "https://github.com/serbanghita/Mobile-Detect/issues", + "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.37" + }, + "funding": [ + { + "url": "https://github.com/serbanghita", + "type": "github" + } + ], + "time": "2021-02-19T21:22:57+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90 <7.0.1", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-12-14T13:15:25+00:00" + }, + { + "name": "morilog/jalali", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/morilog/jalali.git", + "reference": "7ea78b84ce3b5546b01217febb2fba4915dac5e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/morilog/jalali/zipball/7ea78b84ce3b5546b01217febb2fba4915dac5e5", + "reference": "7ea78b84ce3b5546b01217febb2fba4915dac5e5", + "shasum": "" + }, + "require": { + "beberlei/assert": "3.*", + "nesbot/carbon": "^1.21 || ^2.0", + "php": "^7.0 | ^7.1 | ^7.2 | ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Morilog\\Jalali\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milad Rey", + "email": "miladr@gmail.com" + }, + { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } + ], + "description": "This Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in PHP applications, based on Jalali (Shamsi) DateTime class.", + "keywords": [ + "Jalali", + "date", + "datetime", + "laravel", + "morilog" + ], + "support": { + "issues": "https://github.com/morilog/jalali/issues", + "source": "https://github.com/morilog/jalali/tree/v3.2.0" + }, + "time": "2020-12-01T21:26:31+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.0" + }, + "time": "2020-07-31T21:01:56+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "46cf3d8498b095bd33727b13fd5707263af99421" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/46cf3d8498b095bd33727b13fd5707263af99421", + "reference": "46cf3d8498b095bd33727b13fd5707263af99421", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-02-15T16:11:48+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.46.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4", + "reference": "2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^3.4 || ^4.0 || ^5.0" + }, + "require-dev": { + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^2.0", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.54", + "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2021-02-24T17:30:44+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + }, + "time": "2020-12-20T10:01:03+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.1" + }, + "time": "2020-11-07T02:01:34+00:00" + }, + { + "name": "php-amqplib/php-amqplib", + "version": "v2.12.1", + "source": { + "type": "git", + "url": "https://github.com/php-amqplib/php-amqplib.git", + "reference": "0eaaa9d5d45335f4342f69603288883388c2fe21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/0eaaa9d5d45335f4342f69603288883388c2fe21", + "reference": "0eaaa9d5d45335f4342f69603288883388c2fe21", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-sockets": "*", + "php": ">=5.6.3", + "phpseclib/phpseclib": "^2.0.0" + }, + "conflict": { + "php": "7.4.0 - 7.4.1" + }, + "replace": { + "videlalvaro/php-amqplib": "self.version" + }, + "require-dev": { + "ext-curl": "*", + "nategood/httpful": "^0.2.20", + "phpunit/phpunit": "^5.7|^6.5|^7.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12-dev" + } + }, + "autoload": { + "psr-4": { + "PhpAmqpLib\\": "PhpAmqpLib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Alvaro Videla", + "role": "Original Maintainer" + }, + { + "name": "Raúl Araya", + "email": "nubeiro@gmail.com", + "role": "Maintainer" + }, + { + "name": "Luke Bakken", + "email": "luke@bakken.io", + "role": "Maintainer" + }, + { + "name": "Ramūnas Dronga", + "email": "github@ramuno.lt", + "role": "Maintainer" } ], - "description": "The Laravel Framework.", - "homepage": "https://laravel.com", + "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", + "homepage": "https://github.com/php-amqplib/php-amqplib/", "keywords": [ - "framework", - "laravel" + "message", + "queue", + "rabbitmq" ], - "time": "2021-02-23T14:27:41+00:00" + "support": { + "issues": "https://github.com/php-amqplib/php-amqplib/issues", + "source": "https://github.com/php-amqplib/php-amqplib/tree/v2.12.1" + }, + "time": "2020-09-25T18:34:58+00:00" }, { - "name": "laravel/tinker", - "version": "v2.6.0", + "name": "phpoption/phpoption", + "version": "1.7.5", "source": { "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88" + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/daae1c43f1300fe88c05d83db6f3d8f76677ad88", - "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0", - "illuminate/contracts": "^6.0|^7.0|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0", - "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4", - "symfony/var-dumper": "^4.3.4|^5.0" + "php": "^5.5.9 || ^7.0 || ^8.0" }, "require-dev": { - "mockery/mockery": "~1.3.3|^1.4.2", - "phpunit/phpunit": "^8.5.8|^9.3.3" - }, - "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Tinker\\TinkerServiceProvider" - ] + "dev-master": "1.7-dev" } }, "autoload": { "psr-4": { - "Laravel\\Tinker\\": "src/" + "PhpOption\\": "src/PhpOption/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" } ], - "description": "Powerful REPL for the Laravel framework.", + "description": "Option Type for PHP", "keywords": [ - "REPL", - "Tinker", - "laravel", - "psysh" + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } ], - "time": "2021-01-26T20:35:18+00:00" + "time": "2020-07-20T17:29:33+00:00" }, { - "name": "league/commonmark", - "version": "1.5.7", + "name": "phpseclib/phpseclib", + "version": "2.0.30", "source": { "type": "git", - "url": "https://github.com/thephpleague/commonmark.git", - "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "136b9ca7eebef78be14abf90d65c5e57b6bc5d36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/11df9b36fd4f1d2b727a73bf14931d81373b9a54", - "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/136b9ca7eebef78be14abf90d65c5e57b6bc5d36", + "reference": "136b9ca7eebef78be14abf90d65c5e57b6bc5d36", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "scrutinizer/ocular": "1.7.*" + "php": ">=5.3.3" }, "require-dev": { - "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.29.2", - "erusev/parsedown": "~1.0", - "ext-json": "*", - "github/gfm": "0.29.0", - "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", - "scrutinizer/ocular": "^1.5", - "symfony/finder": "^4.2" + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." }, - "bin": [ - "bin/commonmark" - ], "type": "library", "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { - "League\\CommonMark\\": "src" + "phpseclib\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Colin O'Dell", - "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com", + "name": "Jim Wigginton", + "email": "terrafrost@php.net", "role": "Lead Developer" - } - ], - "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", - "homepage": "https://commonmark.thephpleague.com", - "keywords": [ - "commonmark", - "flavored", - "gfm", - "github", - "github-flavored", - "markdown", - "md", - "parser" - ], - "funding": [ + }, { - "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", - "type": "custom" + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" }, { - "url": "https://www.colinodell.com/sponsor", - "type": "custom" + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" }, { - "url": "https://www.paypal.me/colinpodell/10.00", - "type": "custom" + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" }, { - "url": "https://github.com/colinodell", + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.30" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", "type": "github" }, { - "url": "https://www.patreon.com/colinodell", + "url": "https://www.patreon.com/phpseclib", "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", "type": "tidelift" } ], - "time": "2020-10-31T13:49:32+00:00" + "time": "2020-12-17T05:42:04+00:00" }, { - "name": "league/flysystem", - "version": "1.1.3", + "name": "psr/cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1222,65 +3138,42 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frenky.net" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "Common interface for caching libraries", "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } + "cache", + "psr", + "psr-6" ], - "time": "2020-08-23T07:39:11+00:00" + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" }, { - "name": "league/mime-type-detection", - "version": "1.7.0", + "name": "psr/container", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", - "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.18", - "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "php": ">=7.2.0" }, "type": "library", "autoload": { "psr-4": { - "League\\MimeTypeDetection\\": "src" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1289,83 +3182,51 @@ ], "authors": [ { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Mime-type detection for Flysystem", - "funding": [ - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2021-01-18T20:58:21+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" }, { - "name": "monolog/monolog", - "version": "2.2.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", - "graylog2/gelf-php": "^1.4.2", - "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "^8.5", - "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", - "swiftmailer/swiftmailer": "^5.3|^6.0" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mbstring": "Allow to work properly with unicode symbols", - "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + "php": ">=7.2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Monolog\\": "src/Monolog" + "Psr\\EventDispatcher\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1374,83 +3235,49 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "https://github.com/Seldaek/monolog", + "description": "Standard interfaces for event handling.", "keywords": [ - "log", - "logging", - "psr-3" - ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } + "events", + "psr", + "psr-14" ], - "time": "2020-12-14T13:15:25+00:00" + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "nesbot/carbon", - "version": "2.45.1", + "name": "psr/http-client", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "528783b188bdb853eb21239b1722831e0f000a8d" + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/528783b188bdb853eb21239b1722831e0f000a8d", - "reference": "528783b188bdb853eb21239b1722831e0f000a8d", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.1.8 || ^8.0", - "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^3.4 || ^4.0 || ^5.0" - }, - "require-dev": { - "doctrine/orm": "^2.7", - "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^2.0", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54", - "phpunit/phpunit": "^7.5.20 || ^8.5.14", - "squizlabs/php_codesniffer": "^3.4" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" }, - "bin": [ - "bin/carbon" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev", - "dev-3.x": "3.x-dev" - }, - "laravel": { - "providers": [ - "Carbon\\Laravel\\ServiceProvider" - ] - }, - "phpstan": { - "includes": [ - "extension.neon" - ] + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Carbon\\": "src/Carbon/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1459,120 +3286,103 @@ ], "authors": [ { - "name": "Brian Nesbitt", - "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" - }, - { - "name": "kylekatarnls", - "homepage": "http://github.com/kylekatarnls" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "http://carbon.nesbot.com", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "date", - "datetime", - "time" - ], - "funding": [ - { - "url": "https://opencollective.com/Carbon", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", - "type": "tidelift" - } + "http", + "http-client", + "psr", + "psr-18" ], - "time": "2021-02-11T18:30:17+00:00" + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.10.4", + "name": "psr/http-message", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "php": ">=5.3.0" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "A PHP parser written in PHP", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "parser", - "php" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" ], - "time": "2020-12-20T10:01:03+00:00" + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" }, { - "name": "opis/closure", - "version": "3.6.1", + "name": "psr/log", + "version": "1.1.3", "source": { "type": "git", - "url": "https://github.com/opis/closure.git", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0" - }, - "require-dev": { - "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Opis\\Closure\\": "src/" - }, - "files": [ - "functions.php" - ] + "Psr\\Log\\": "Psr/Log/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1580,117 +3390,122 @@ ], "authors": [ { - "name": "Marius Sarca", - "email": "marius.sarca@gmail.com" - }, - { - "name": "Sorin Sarca", - "email": "sarca_sorin@hotmail.com" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", - "homepage": "https://opis.io/closure", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "anonymous functions", - "closure", - "function", - "serializable", - "serialization", - "serialize" + "log", + "psr", + "psr-3" ], - "time": "2020-11-07T02:01:34+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" }, - { - "name": "phpoption/phpoption", - "version": "1.7.5", + { + "name": "psr/simple-cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", - "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "PhpOption\\": "src/PhpOption/" + "Psr\\SimpleCache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "Option Type for PHP", + "description": "Common interfaces for simple caching", "keywords": [ - "language", - "option", - "php", - "type" - ], - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", - "type": "tidelift" - } + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" ], - "time": "2020-07-20T17:29:33+00:00" + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "psy/psysh", + "version": "v0.10.6", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/bobthecow/psysh.git", + "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3", + "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3", "shasum": "" }, "require": { - "php": ">=5.3.0" + "dnoegel/php-xdg-base-dir": "0.1.*", + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", + "php": "^8.0 || ^7.0 || ^5.5.9", + "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", + "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "hoa/console": "3.17.*" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." }, + "bin": [ + "bin/psysh" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "0.10.x-dev" } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "Psr\\Container\\": "src/" + "Psy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1699,48 +3514,51 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "REPL", + "console", + "interactive", + "shell" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.10.6" + }, + "time": "2021-01-18T15:53:43+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=5.6" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, + "type": "library", "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } + "files": [ + "src/getallheaders.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1748,45 +3566,56 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" } ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "time": "2019-01-08T18:20:26+00:00" + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" }, { - "name": "psr/http-client", - "version": "1.0.1", + "name": "ramsey/collection", + "version": "1.1.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "url": "https://github.com/ramsey/collection.git", + "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", "shasum": "" }, "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "php": "^7.2 || ^8" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Http\\Client\\": "src/" + "Ramsey\\Collection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1795,96 +3624,159 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", + "description": "A PHP 7.2+ library for representing and manipulating collections.", "keywords": [ - "http", - "http-client", - "psr", - "psr-18" + "array", + "collection", + "hash", + "map", + "queue", + "set" ], - "time": "2020-06-29T06:28:15+00:00" + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-01-21T17:40:04+00:00" }, { - "name": "psr/http-message", - "version": "1.0.1", + "name": "ramsey/uuid", + "version": "4.1.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "url": "https://github.com/ramsey/uuid.git", + "reference": "cd4032040a750077205918c86049aa0f43d22947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", + "reference": "cd4032040a750077205918c86049aa0f43d22947", "shasum": "" }, - "require": { - "php": ">=5.3.0" + "require": { + "brick/math": "^0.8 || ^0.9", + "ext-json": "*", + "php": "^7.2 || ^8", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", + "doctrine/annotations": "^1.8", + "goaop/framework": "^2", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-mockery": "^1.3", + "php-mock/php-mock-phpunit": "^2.5", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^0.17.1", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "psy/psysh": "^0.10.0", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "3.9.4" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "4.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" - } + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid" + }, + "funding": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "url": "https://github.com/ramsey", + "type": "github" } ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "time": "2016-08-06T14:39:51+00:00" + "time": "2020-08-18T17:17:46+00:00" }, { - "name": "psr/log", - "version": "1.1.3", + "name": "spatie/image", + "version": "1.10.2", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "url": "https://github.com/spatie/image.git", + "reference": "12662673fbe649bffcd3a24188a404dc31fa118c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/spatie/image/zipball/12662673fbe649bffcd3a24188a404dc31fa118c", + "reference": "12662673fbe649bffcd3a24188a404dc31fa118c", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-exif": "*", + "ext-mbstring": "*", + "league/glide": "^1.6", + "php": "^7.2|^8.0", + "spatie/image-optimizer": "^1.1", + "spatie/temporary-directory": "^1.0", + "symfony/process": "^3.0|^4.0|^5.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^8.0|^9.0", + "symfony/var-dumper": "^4.0|^5.0" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Spatie\\Image\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1893,45 +3785,62 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Manipulate images with an expressive API", + "homepage": "https://github.com/spatie/image", "keywords": [ - "log", - "psr", - "psr-3" + "image", + "spatie" ], - "time": "2020-03-23T09:12:05+00:00" + "support": { + "issues": "https://github.com/spatie/image/issues", + "source": "https://github.com/spatie/image/tree/1.10.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-01-26T07:53:19+00:00" }, { - "name": "psr/simple-cache", - "version": "1.0.1", + "name": "spatie/image-optimizer", + "version": "1.3.2", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "url": "https://github.com/spatie/image-optimizer.git", + "reference": "6aa170eb292758553d332efee5e0c3977341080c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/6aa170eb292758553d332efee5e0c3977341080c", + "reference": "6aa170eb292758553d332efee5e0c3977341080c", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-fileinfo": "*", + "php": "^7.2|^8.0", + "psr/log": "^1.0", + "symfony/process": "^4.2|^5.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^8.0|^9.0", + "symfony/var-dumper": "^4.2|^5.0" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\SimpleCache\\": "src/" + "Spatie\\ImageOptimizer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1940,69 +3849,86 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "Common interfaces for simple caching", + "description": "Easily optimize images using PHP", + "homepage": "https://github.com/spatie/image-optimizer", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "image-optimizer", + "spatie" ], - "time": "2017-10-23T01:57:42+00:00" + "support": { + "issues": "https://github.com/spatie/image-optimizer/issues", + "source": "https://github.com/spatie/image-optimizer/tree/1.3.2" + }, + "time": "2020-11-28T12:37:58+00:00" }, { - "name": "psy/psysh", - "version": "v0.10.6", + "name": "spatie/laravel-medialibrary", + "version": "9.4.3", "source": { "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3" + "url": "https://github.com/spatie/laravel-medialibrary.git", + "reference": "2eae7416a6d7762e147f26f6cac7cf099eda109b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3", - "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/2eae7416a6d7762e147f26f6cac7cf099eda109b", + "reference": "2eae7416a6d7762e147f26f6cac7cf099eda109b", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1.*", + "ext-exif": "*", + "ext-fileinfo": "*", "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", - "php": "^8.0 || ^7.0 || ^5.5.9", - "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", - "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + "illuminate/bus": "^7.0|^8.0", + "illuminate/console": "^7.0|^8.0", + "illuminate/database": "^7.0|^8.0", + "illuminate/pipeline": "^7.0|^8.0", + "illuminate/support": "^7.0|^8.0", + "league/flysystem": "^1.0.64", + "maennchen/zipstream-php": "^1.0|^2.0", + "php": "^7.4|^8.0", + "spatie/image": "^1.4.0", + "spatie/temporary-directory": "^1.1", + "symfony/console": "^4.4|^5.0" + }, + "conflict": { + "php-ffmpeg/php-ffmpeg": "<0.6.1" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.*" + "aws/aws-sdk-php": "^3.133.11", + "doctrine/dbal": "^2.5.2", + "ext-pdo_sqlite": "*", + "ext-zip": "*", + "guzzlehttp/guzzle": "^6.3|^7.0", + "league/flysystem-aws-s3-v3": "^1.0.23", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^5.0|^6.0", + "phpunit/phpunit": "^9.1", + "spatie/pdf-to-image": "^2.0", + "spatie/phpunit-snapshot-assertions": "^4.0" }, "suggest": { - "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", - "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage", + "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails", + "spatie/pdf-to-image": "Required for generating thumbsnails of PDFs and SVGs" }, - "bin": [ - "bin/psysh" - ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.10.x-dev" + "laravel": { + "providers": [ + "Spatie\\MediaLibrary\\MediaLibraryServiceProvider" + ] } }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "Psy\\": "src/" + "Spatie\\MediaLibrary\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2011,100 +3937,78 @@ ], "authors": [ { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "description": "Associate files with Eloquent models", + "homepage": "https://github.com/spatie/laravel-medialibrary", "keywords": [ - "REPL", - "console", - "interactive", - "shell" + "cms", + "conversion", + "downloads", + "images", + "laravel", + "laravel-medialibrary", + "media", + "spatie" ], - "time": "2021-01-18T15:53:43+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] + "support": { + "issues": "https://github.com/spatie/laravel-medialibrary/issues", + "source": "https://github.com/spatie/laravel-medialibrary/tree/9.4.3" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" } ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" + "time": "2021-03-07T18:42:19+00:00" }, { - "name": "ramsey/collection", - "version": "1.1.3", + "name": "spatie/laravel-query-builder", + "version": "3.3.4", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + "url": "https://github.com/spatie/laravel-query-builder.git", + "reference": "2e131b0c8ae600b6e3aabb5a1501c721862a0b8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/2e131b0c8ae600b6e3aabb5a1501c721862a0b8f", + "reference": "2e131b0c8ae600b6e3aabb5a1501c721862a0b8f", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "illuminate/database": "^6.0|^7.0|^8.0", + "illuminate/http": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.3|^8.0" }, "require-dev": { - "captainhook/captainhook": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.6", - "fakerphp/faker": "^1.5", - "hamcrest/hamcrest-php": "^2", - "jangregor/phpstan-prophecy": "^0.8", - "mockery/mockery": "^1.3", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12.32", - "phpstan/phpstan-mockery": "^0.12.5", - "phpstan/phpstan-phpunit": "^0.12.11", - "phpunit/phpunit": "^8.5 || ^9", - "psy/psysh": "^0.10.4", - "slevomat/coding-standard": "^6.3", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.4" + "ext-json": "*", + "laravel/legacy-factories": "^1.0.4", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^4.9|^5.8|^6.3", + "phpunit/phpunit": "^9.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\QueryBuilder\\QueryBuilderServiceProvider" + ] + } + }, "autoload": { "psr-4": { - "Ramsey\\Collection\\": "src/" + "Spatie\\QueryBuilder\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2113,131 +4017,93 @@ ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", + "description": "Easily build Eloquent queries from API requests", + "homepage": "https://github.com/spatie/laravel-query-builder", "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "laravel-query-builder", + "spatie" ], + "support": { + "issues": "https://github.com/spatie/laravel-query-builder/issues", + "source": "https://github.com/spatie/laravel-query-builder" + }, "funding": [ { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" + "url": "https://spatie.be/open-source/support-us", + "type": "custom" } ], - "time": "2021-01-21T17:40:04+00:00" + "time": "2020-11-26T14:51:30+00:00" }, { - "name": "ramsey/uuid", - "version": "4.1.1", + "name": "spatie/temporary-directory", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "cd4032040a750077205918c86049aa0f43d22947" + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "f517729b3793bca58f847c5fd383ec16f03ffec6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", - "reference": "cd4032040a750077205918c86049aa0f43d22947", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/f517729b3793bca58f847c5fd383ec16f03ffec6", + "reference": "f517729b3793bca58f847c5fd383ec16f03ffec6", "shasum": "" }, "require": { - "brick/math": "^0.8 || ^0.9", - "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "php": "^7.2|^8.0" }, "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", - "doctrine/annotations": "^1.8", - "goaop/framework": "^2", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^0.17.1", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "phpunit/phpunit": "^8.0|^9.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, - "files": [ - "src/functions.php" - ] + "Spatie\\TemporaryDirectory\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "funding": [ + "authors": [ { - "url": "https://github.com/ramsey", - "type": "github" + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" } ], - "time": "2020-08-18T17:17:46+00:00" + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/1.3.0" + }, + "time": "2020-11-09T15:54:21+00:00" }, { "name": "swiftmailer/swiftmailer", - "version": "v6.2.5", + "version": "v6.2.6", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7" + "reference": "d2791ff0b73247cdc2096b14f5580aba40c12bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7", - "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d2791ff0b73247cdc2096b14f5580aba40c12bff", + "reference": "d2791ff0b73247cdc2096b14f5580aba40c12bff", "shasum": "" }, "require": { @@ -2285,6 +4151,10 @@ "mail", "mailer" ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.6" + }, "funding": [ { "url": "https://github.com/fabpot", @@ -2295,20 +4165,20 @@ "type": "tidelift" } ], - "time": "2021-01-12T09:35:59+00:00" + "time": "2021-03-05T12:08:49+00:00" }, { "name": "symfony/console", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + "reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "url": "https://api.github.com/repos/symfony/console/zipball/d6d0cc30d8c0fda4e7b213c20509b0159a8f4556", + "reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556", "shasum": "" }, "require": { @@ -2375,6 +4245,9 @@ "console", "terminal" ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2389,11 +4262,11 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-23T10:08:49+00:00" }, { "name": "symfony/css-selector", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2437,6 +4310,9 @@ ], "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2501,6 +4377,9 @@ ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/master" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2519,16 +4398,16 @@ }, { "name": "symfony/error-handler", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d" + "reference": "b547d3babcab5c31e01de59ee33e9d9c1421d7d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48f18b3609e120ea66d59142c23dc53e9562c26d", - "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/b547d3babcab5c31e01de59ee33e9d9c1421d7d0", + "reference": "b547d3babcab5c31e01de59ee33e9d9c1421d7d0", "shasum": "" }, "require": { @@ -2567,6 +4446,9 @@ ], "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2581,20 +4463,20 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-11T08:21:20+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" + "reference": "d08d6ec121a425897951900ab692b612a61d6240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d08d6ec121a425897951900ab692b612a61d6240", + "reference": "d08d6ec121a425897951900ab692b612a61d6240", "shasum": "" }, "require": { @@ -2649,6 +4531,9 @@ ], "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2663,7 +4548,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:36:42+00:00" + "time": "2021-02-18T17:12:37+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2725,6 +4610,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2743,16 +4631,16 @@ }, { "name": "symfony/finder", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4adc8d172d602008c204c2e16956f99257248e03" + "reference": "0d639a0943822626290d169965804f79400e6a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", - "reference": "4adc8d172d602008c204c2e16956f99257248e03", + "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", + "reference": "0d639a0943822626290d169965804f79400e6a04", "shasum": "" }, "require": { @@ -2783,6 +4671,9 @@ ], "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2797,7 +4688,7 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-15T18:55:04+00:00" }, { "name": "symfony/http-client-contracts", @@ -2859,6 +4750,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2877,16 +4771,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36" + "reference": "54499baea7f7418bce7b5ec92770fd0799e8e9bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/20c554c0f03f7cde5ce230ed248470cccbc34c36", - "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/54499baea7f7418bce7b5ec92770fd0799e8e9bf", + "reference": "54499baea7f7418bce7b5ec92770fd0799e8e9bf", "shasum": "" }, "require": { @@ -2929,6 +4823,9 @@ ], "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2943,20 +4840,20 @@ "type": "tidelift" } ], - "time": "2021-02-03T04:42:09+00:00" + "time": "2021-02-25T17:16:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05" + "reference": "c452dbe4f385f030c3957821bf921b13815d6140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", - "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c452dbe4f385f030c3957821bf921b13815d6140", + "reference": "c452dbe4f385f030c3957821bf921b13815d6140", "shasum": "" }, "require": { @@ -2991,7 +4888,7 @@ "psr/log-implementation": "1.0" }, "require-dev": { - "psr/cache": "~1.0", + "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^4.4|^5.0", "symfony/config": "^5.0", "symfony/console": "^4.4|^5.0", @@ -3038,6 +4935,9 @@ ], "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3052,20 +4952,20 @@ "type": "tidelift" } ], - "time": "2021-02-03T04:51:58+00:00" + "time": "2021-03-04T18:05:55+00:00" }, { "name": "symfony/mime", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86" + "reference": "5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", - "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", + "url": "https://api.github.com/repos/symfony/mime/zipball/5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd", + "reference": "5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd", "shasum": "" }, "require": { @@ -3117,6 +5017,9 @@ "mime", "mime-type" ], + "support": { + "source": "https://github.com/symfony/mime/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3131,7 +5034,7 @@ "type": "tidelift" } ], - "time": "2021-02-02T06:10:15+00:00" + "time": "2021-02-15T18:55:04+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3193,6 +5096,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3270,6 +5176,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3348,6 +5257,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3432,6 +5344,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3513,6 +5428,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3590,6 +5508,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3663,6 +5584,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3739,6 +5663,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3819,6 +5746,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3837,7 +5767,7 @@ }, { "name": "symfony/process", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -3878,6 +5808,9 @@ ], "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3896,16 +5829,16 @@ }, { "name": "symfony/routing", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661" + "reference": "cafa138128dfd6ab6be1abf6279169957b34f662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/348b5917e56546c6d96adbf21d7f92c9ef563661", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661", + "url": "https://api.github.com/repos/symfony/routing/zipball/cafa138128dfd6ab6be1abf6279169957b34f662", + "reference": "cafa138128dfd6ab6be1abf6279169957b34f662", "shasum": "" }, "require": { @@ -3965,6 +5898,9 @@ "uri", "url" ], + "support": { + "source": "https://github.com/symfony/routing/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -3979,7 +5915,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-02-22T15:48:39+00:00" }, { "name": "symfony/service-contracts", @@ -4041,6 +5977,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4059,16 +5998,16 @@ }, { "name": "symfony/string", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "c95468897f408dd0aca2ff582074423dd0455122" + "reference": "4e78d7d47061fa183639927ec40d607973699609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", - "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "url": "https://api.github.com/repos/symfony/string/zipball/4e78d7d47061fa183639927ec40d607973699609", + "reference": "4e78d7d47061fa183639927ec40d607973699609", "shasum": "" }, "require": { @@ -4121,6 +6060,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4135,20 +6077,20 @@ "type": "tidelift" } ], - "time": "2021-01-25T15:14:59+00:00" + "time": "2021-02-16T10:20:28+00:00" }, { "name": "symfony/translation", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949" + "reference": "74b0353ab34ff4cca827a2cf909e325d96815e60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/c021864d4354ee55160ddcfd31dc477a1bc77949", - "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949", + "url": "https://api.github.com/repos/symfony/translation/zipball/74b0353ab34ff4cca827a2cf909e325d96815e60", + "reference": "74b0353ab34ff4cca827a2cf909e325d96815e60", "shasum": "" }, "require": { @@ -4165,7 +6107,7 @@ "symfony/yaml": "<4.4" }, "provide": { - "symfony/translation-implementation": "2.0" + "symfony/translation-implementation": "2.3" }, "require-dev": { "psr/log": "~1.0", @@ -4211,6 +6153,9 @@ ], "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4225,7 +6170,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-03-04T15:41:09+00:00" }, { "name": "symfony/translation-contracts", @@ -4286,6 +6231,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4304,16 +6252,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694" + "reference": "6a81fec0628c468cf6d5c87a4d003725e040e223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72ca213014a92223a5d18651ce79ef441c12b694", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6a81fec0628c468cf6d5c87a4d003725e040e223", + "reference": "6a81fec0628c468cf6d5c87a4d003725e040e223", "shasum": "" }, "require": { @@ -4371,6 +6319,9 @@ "debug", "dump" ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4385,7 +6336,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-02-18T23:11:19+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -4434,8 +6385,92 @@ ], "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + }, "time": "2020-07-13T06:12:54+00:00" }, + { + "name": "torann/geoip", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/Torann/laravel-geoip.git", + "reference": "f16d5df66ecb6ba4ffaef52abef519fbc19596d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Torann/laravel-geoip/zipball/f16d5df66ecb6ba4ffaef52abef519fbc19596d3", + "reference": "f16d5df66ecb6ba4ffaef52abef519fbc19596d3", + "shasum": "" + }, + "require": { + "illuminate/cache": "^8.0", + "illuminate/console": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "geoip2/geoip2": "~2.1", + "mockery/mockery": "^1.3", + "phpstan/phpstan": "^0.12.14", + "phpunit/phpunit": "^8.0", + "squizlabs/php_codesniffer": "^3.5", + "vlucas/phpdotenv": "^5.0" + }, + "suggest": { + "geoip2/geoip2": "Required to use the MaxMind database or web service with GeoIP (~2.1).", + "monolog/monolog": "Allows for storing location not found errors to the log" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Torann\\GeoIP\\GeoIPServiceProvider" + ], + "aliases": { + "GeoIP": "Torann\\GeoIP\\Facades\\GeoIP" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Torann\\GeoIP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Daniel Stainback", + "email": "torann@gmail.com" + } + ], + "description": "Support for multiple GeoIP services.", + "keywords": [ + "IP API", + "geoip", + "geolocation", + "infoDB", + "laravel", + "location", + "maxmind" + ], + "support": { + "issues": "https://github.com/Torann/laravel-geoip/issues", + "source": "https://github.com/Torann/laravel-geoip/tree/3.0.2" + }, + "time": "2020-12-21T19:12:11+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.3.0", @@ -4500,6 +6535,10 @@ "env", "environment" ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -4558,6 +6597,10 @@ "clean", "php" ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + }, "funding": [ { "url": "https://www.paypal.me/moelleken", @@ -4629,10 +6672,77 @@ "check", "validate" ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, "time": "2020-07-08T17:02:28+00:00" } ], "packages-dev": [ + { + "name": "andreaselia/laravel-api-to-postman", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/AndreasElia/laravel-api-to-postman.git", + "reference": "c40892559137ac356268afb03d3e279ff3efb188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/AndreasElia/laravel-api-to-postman/zipball/c40892559137ac356268afb03d3e279ff3efb188", + "reference": "c40892559137ac356268afb03d3e279ff3efb188", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/config": "^6.0|^7.0|^8.0", + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/contracts": "^6.0|^7.0|^8.0", + "illuminate/routing": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.12" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "AndreasElia\\PostmanGenerator\\PostmanGeneratorServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "AndreasElia\\PostmanGenerator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Elia", + "email": "andreaselia@live.co.uk" + } + ], + "description": "Generate a Postman collection automatically from your Laravel API", + "keywords": [ + "Postman", + "api", + "collection", + "generate", + "laravel" + ], + "support": { + "issues": "https://github.com/AndreasElia/laravel-api-to-postman/issues", + "source": "https://github.com/AndreasElia/laravel-api-to-postman/tree/v1.7.2" + }, + "time": "2021-02-28T23:49:29+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.0", @@ -4682,6 +6792,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -4751,6 +6865,10 @@ "flare", "reporting" ], + "support": { + "issues": "https://github.com/facade/flare-client-php/issues", + "source": "https://github.com/facade/flare-client-php/tree/1.4.0" + }, "funding": [ { "url": "https://github.com/spatie", @@ -4761,16 +6879,16 @@ }, { "name": "facade/ignition", - "version": "2.5.13", + "version": "2.5.14", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e" + "reference": "17097f7a83e200d90d1cf9f4d1b35c1001513a47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/5e9ef386aaad9985cee2ac23281a27568d083b7e", - "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e", + "url": "https://api.github.com/repos/facade/ignition/zipball/17097f7a83e200d90d1cf9f4d1b35c1001513a47", + "reference": "17097f7a83e200d90d1cf9f4d1b35c1001513a47", "shasum": "" }, "require": { @@ -4828,7 +6946,13 @@ "laravel", "page" ], - "time": "2021-02-16T12:46:19+00:00" + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/facade/ignition/issues", + "source": "https://github.com/facade/ignition" + }, + "time": "2021-03-04T08:48:01+00:00" }, { "name": "facade/ignition-contracts", @@ -4877,6 +7001,10 @@ "flare", "ignition" ], + "support": { + "issues": "https://github.com/facade/ignition-contracts/issues", + "source": "https://github.com/facade/ignition-contracts/tree/1.0.2" + }, "time": "2020-10-16T08:27:54+00:00" }, { @@ -4925,6 +7053,10 @@ "faker", "fixtures" ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.13.0" + }, "time": "2020-12-18T16:50:48+00:00" }, { @@ -4986,6 +7118,10 @@ "throwable", "whoops" ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.9.2" + }, "funding": [ { "url": "https://github.com/denis-sokolov", @@ -5039,20 +7175,24 @@ "keywords": [ "test" ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, "time": "2020-07-09T08:09:16+00:00" }, { "name": "laravel/sail", - "version": "v1.4.3", + "version": "v1.4.6", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "0200ce6e0f697699bce036c42d91f1daab8039a8" + "reference": "59ee7e2b2efeb644eabea719186db91d11666733" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/0200ce6e0f697699bce036c42d91f1daab8039a8", - "reference": "0200ce6e0f697699bce036c42d91f1daab8039a8", + "url": "https://api.github.com/repos/laravel/sail/zipball/59ee7e2b2efeb644eabea719186db91d11666733", + "reference": "59ee7e2b2efeb644eabea719186db91d11666733", "shasum": "" }, "require": { @@ -5095,7 +7235,11 @@ "docker", "laravel" ], - "time": "2021-02-24T21:20:16+00:00" + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2021-03-03T15:22:44+00:00" }, { "name": "mockery/mockery", @@ -5163,6 +7307,10 @@ "test double", "testing" ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.4.3" + }, "time": "2021-02-24T09:51:49+00:00" }, { @@ -5211,6 +7359,10 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", @@ -5287,6 +7439,10 @@ "php", "symfony" ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, "funding": [ { "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", @@ -5357,6 +7513,10 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, "time": "2020-06-27T14:33:11+00:00" }, { @@ -5404,6 +7564,10 @@ } ], "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, "time": "2021-02-23T14:00:09+00:00" }, { @@ -5453,6 +7617,10 @@ "reflection", "static analysis" ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, "time": "2020-06-27T09:03:43+00:00" }, { @@ -5505,6 +7673,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, "time": "2020-09-03T19:13:55+00:00" }, { @@ -5550,6 +7722,10 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, "time": "2020-09-17T18:55:26+00:00" }, { @@ -5613,6 +7789,10 @@ "spy", "stub" ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + }, "time": "2020-12-19T10:15:11+00:00" }, { @@ -5680,6 +7860,10 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -5736,6 +7920,10 @@ "filesystem", "iterator" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -5795,6 +7983,10 @@ "keywords": [ "process" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -5850,6 +8042,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -5905,6 +8101,10 @@ "keywords": [ "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6000,6 +8200,10 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2" + }, "funding": [ { "url": "https://phpunit.de/donate.html", @@ -6056,6 +8260,10 @@ ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6108,6 +8316,10 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6159,6 +8371,10 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6229,6 +8445,10 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6282,6 +8502,10 @@ ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6344,6 +8568,10 @@ "unidiff", "unified diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6403,6 +8631,10 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6476,6 +8708,10 @@ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6536,6 +8772,10 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6589,6 +8829,10 @@ ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6642,6 +8886,10 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6693,6 +8941,10 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6752,6 +9004,10 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6803,6 +9059,10 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6855,6 +9115,10 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6904,6 +9168,10 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -6950,6 +9218,10 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, "funding": [ { "url": "https://github.com/theseer", @@ -6965,8 +9237,10 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { + "ext-gd": "*", + "ext-json": "*", "php": "^7.3|^8.0" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/config/amqp.php b/config/amqp.php new file mode 100644 index 0000000..6c58ccc --- /dev/null +++ b/config/amqp.php @@ -0,0 +1,73 @@ + env('AMQP_CONNECTION', 'rabbitmq'), + + /*Available connections*/ + 'connections' => [ + + 'rabbitmq' => [ + 'connection' => [ + 'host' => env('AMQP_HOST', 'liwo_rabbitmq_1'), + 'port' => env('AMQP_PORT', 5672), + 'username' => env('AMQP_USERNAME', 'root'), + 'password' => env('AMQP_PASSWORD', 'root'), + 'vhost' => env('AMQP_VHOST', '/'), + 'connect_options' => [], + 'ssl_options' => [], + 'ssl_protocol' => env('AMQP_SSL_PROTOCOL', 'ssl'), + ], + + 'channel_id' => null, + + 'message' => [ + 'content_type' => 'text/plain', + 'delivery_mode' => env('AMQP_MESSAGE_DELIVERY_MODE', AMQPMessage::DELIVERY_MODE_PERSISTENT), + 'content_encoding' => 'UTF-8', + ], + + 'exchange' => [ + 'name' => env('AMQP_EXCHANGE_NAME', 'activity_exchange'), + 'declare' => env('AMQP_EXCHANGE_DECLARE', false), + 'type' => env('AMQP_EXCHANGE_TYPE', 'headers'), + 'passive' => env('AMQP_EXCHANGE_PASSIVE', false), + 'durable' => env('AMQP_EXCHANGE_DURABLE', true), + 'auto_delete' => env('AMQP_EXCHANGE_AUTO_DEL', false), + 'internal' => env('AMQP_EXCHANGE_INTERNAL', false), + 'nowait' => env('AMQP_EXCHANGE_NOWAIT', false), + 'properties' => [], + ], + + 'queue' => [ + 'declare' => env('AMQP_QUEUE_DECLARE', false), + 'passive' => env('AMQP_QUEUE_PASSIVE', false), + 'durable' => env('AMQP_QUEUE_DURABLE', true), + 'exclusive' => env('AMQP_QUEUE_EXCLUSIVE', false), + 'auto_delete' => env('AMQP_QUEUE_AUTO_DEL', false), + 'nowait' => env('AMQP_QUEUE_NOWAIT', false), + 'd_properties' => [], // queue_declare properties/arguments + 'b_properties' => [], // queue_bind properties/arguments + ], + + 'consumer' => [ + 'tag' => env('AMQP_CONSUMER_TAG', ''), + 'no_local' => env('AMQP_CONSUMER_NO_LOCAL', false), + 'no_ack' => env('AMQP_CONSUMER_NO_ACK', false), + 'exclusive' => env('AMQP_CONSUMER_EXCLUSIVE', false), + 'nowait' => env('AMQP_CONSUMER_NOWAIT', false), + 'ticket' => null, + 'properties' => [], + ], + + 'qos' => [ + 'enabled' => env('AMQP_QOS_ENABLED', false), + 'qos_prefetch_size' => env('AMQP_QOS_PREF_SIZE', 0), + 'qos_prefetch_count' => env('AMQP_QOS_PREF_COUNT', 1), + 'qos_a_global' => env('AMQP_QOS_GLOBAL', false), + ], + ], + ], +]; diff --git a/config/api-postman.php b/config/api-postman.php new file mode 100644 index 0000000..7d9727b --- /dev/null +++ b/config/api-postman.php @@ -0,0 +1,122 @@ + env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Collection Filename + |-------------------------------------------------------------------------- + | + | The name for the collection file to be saved. + | + */ + + 'filename' => '{timestamp}_{app}_collection.json', + + /* + |-------------------------------------------------------------------------- + | Structured + |-------------------------------------------------------------------------- + | + | If you want folders to be generated based on namespace. + | + */ + + 'structured' => true, + + /* + |-------------------------------------------------------------------------- + | Auth Middleware + |-------------------------------------------------------------------------- + | + | The middleware which wraps your authenticated API routes. + | + | E.g. auth:api, auth:sanctum + | + */ + + 'auth_middleware' => 'auth:api', + + /* + |-------------------------------------------------------------------------- + | Headers + |-------------------------------------------------------------------------- + | + | The headers applied to all routes within the collection. + | + */ + + 'headers' => [ + [ + 'key' => 'Accept', + 'value' => 'application/json', + ], + [ + 'key' => 'Content-Type', + 'value' => 'application/json', + ], + [ + 'key' => 'Authorization', + 'value' => 'Bearer SBzUKamo6CrzQD0hzys2zDxeXiHmPnQcCmlmtCgHAo0JRglIApqJuFF0zxIY', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Enable Form Data + |-------------------------------------------------------------------------- + | + | Determines whether or not form data should be handled. + | + */ + + 'enable_formdata' => false, + + /* + |-------------------------------------------------------------------------- + | Form Data + |-------------------------------------------------------------------------- + | + | The key/values to requests for form data dummy information. + | + */ + + 'formdata' => [ + // 'email' => 'john@example.com', + // 'password' => 'changeme', + ], + + /* + |-------------------------------------------------------------------------- + | Include Middleware + |-------------------------------------------------------------------------- + | + | The routes of the included middleware are included in the export. + | + */ + + 'include_middleware' => ['api'], + + /* + |-------------------------------------------------------------------------- + | Disk Driver + |-------------------------------------------------------------------------- + | + | Specify the configured disk for storing the postman collection file. + | + */ + + 'disk' => 'local', + +]; diff --git a/config/app.php b/config/app.php index 2a2f0eb..d517cdc 100644 --- a/config/app.php +++ b/config/app.php @@ -2,138 +2,28 @@ return [ - /* - |-------------------------------------------------------------------------- - | Application Name - |-------------------------------------------------------------------------- - | - | This value is the name of your application. This value is used when the - | framework needs to place the application's name in a notification or - | any other location as required by the application or its packages. - | - */ - 'name' => env('APP_NAME', 'Laravel'), - /* - |-------------------------------------------------------------------------- - | Application Environment - |-------------------------------------------------------------------------- - | - | This value determines the "environment" your application is currently - | running in. This may determine how you prefer to configure various - | services the application utilizes. Set this in your ".env" file. - | - */ - 'env' => env('APP_ENV', 'production'), - /* - |-------------------------------------------------------------------------- - | Application Debug Mode - |-------------------------------------------------------------------------- - | - | When your application is in debug mode, detailed error messages with - | stack traces will be shown on every error that occurs within your - | application. If disabled, a simple generic error page is shown. - | - */ - 'debug' => (bool) env('APP_DEBUG', false), - /* - |-------------------------------------------------------------------------- - | Application URL - |-------------------------------------------------------------------------- - | - | This URL is used by the console to properly generate URLs when using - | the Artisan command line tool. You should set this to the root of - | your application so that it is used when running Artisan tasks. - | - */ - 'url' => env('APP_URL', 'http://localhost'), 'asset_url' => env('ASSET_URL', null), - /* - |-------------------------------------------------------------------------- - | Application Timezone - |-------------------------------------------------------------------------- - | - | Here you may specify the default timezone for your application, which - | will be used by the PHP date and date-time functions. We have gone - | ahead and set this to a sensible default for you out of the box. - | - */ - - 'timezone' => 'UTC', - - /* - |-------------------------------------------------------------------------- - | Application Locale Configuration - |-------------------------------------------------------------------------- - | - | The application locale determines the default locale that will be used - | by the translation service provider. You are free to set this value - | to any of the locales which will be supported by the application. - | - */ - - 'locale' => 'en', - - /* - |-------------------------------------------------------------------------- - | Application Fallback Locale - |-------------------------------------------------------------------------- - | - | The fallback locale determines the locale to use when the current one - | is not available. You may change the value to correspond to any of - | the language folders that are provided through your application. - | - */ + 'timezone' => 'Asia/Tehran', + + 'locale' => 'fa', 'fallback_locale' => 'en', - /* - |-------------------------------------------------------------------------- - | Faker Locale - |-------------------------------------------------------------------------- - | - | This locale will be used by the Faker PHP library when generating fake - | data for your database seeds. For example, this will be used to get - | localized telephone numbers, street address information and more. - | - */ - - 'faker_locale' => 'en_US', - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - | This key is used by the Illuminate encrypter service and should be set - | to a random, 32 character string, otherwise these encrypted strings - | will not be safe. Please do this before deploying an application! - | - */ + 'faker_locale' => 'fa_IR', 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', - /* - |-------------------------------------------------------------------------- - | Autoloaded Service Providers - |-------------------------------------------------------------------------- - | - | The service providers listed here will be automatically loaded on the - | request to your application. Feel free to add your own services to - | this array to grant expanded functionality to your applications. - | - */ - 'providers' => [ /* @@ -171,23 +61,12 @@ return [ */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, - // App\Providers\BroadcastServiceProvider::class, + App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, - + App\Utilities\Zarinpal\Laravel\ZarinpalServiceProvider::class, ], - /* - |-------------------------------------------------------------------------- - | Class Aliases - |-------------------------------------------------------------------------- - | - | This array of class aliases will be registered when this application - | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded so they don't hinder performance. - | - */ - 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, @@ -215,7 +94,7 @@ return [ 'Password' => Illuminate\Support\Facades\Password::class, 'Queue' => Illuminate\Support\Facades\Queue::class, 'Redirect' => Illuminate\Support\Facades\Redirect::class, - // 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, 'Request' => Illuminate\Support\Facades\Request::class, 'Response' => Illuminate\Support\Facades\Response::class, 'Route' => Illuminate\Support\Facades\Route::class, @@ -227,6 +106,6 @@ return [ 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, + 'View' => App\Utilities\Zarinpal\Laravel\Facade\Zarinpal::class, ], - ]; diff --git a/config/cors.php b/config/cors.php index 8a39e6d..b2930d0 100644 --- a/config/cors.php +++ b/config/cors.php @@ -21,7 +21,7 @@ return [ 'allowed_origins' => ['*'], - 'allowed_origins_patterns' => [], + 'allowed_origins_patterns' => ['Content-Type', 'X-Requested-With'], 'allowed_headers' => ['*'], diff --git a/config/cors.php.laravel b/config/cors.php.laravel new file mode 100644 index 0000000..c3ded3c --- /dev/null +++ b/config/cors.php.laravel @@ -0,0 +1,59 @@ + ['/*'], + + /* + * Matches the request method. `['*']` allows all methods. + */ + 'allowed_methods' => ['*'], + + /* + * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => ['*'], + + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowedOriginsPatterns' => ['Content-Type', 'X-Requested-With'], + + /* + * Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers. + */ + 'allowed_headers' => ['*'], + + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], + + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, + + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => false, +]; diff --git a/config/cors.php.lumen b/config/cors.php.lumen new file mode 100644 index 0000000..8a39e6d --- /dev/null +++ b/config/cors.php.lumen @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/fcm.php b/config/fcm.php new file mode 100644 index 0000000..9105e5e --- /dev/null +++ b/config/fcm.php @@ -0,0 +1,18 @@ + env('FCM_KEY'), + +]; diff --git a/config/filesystems.php b/config/filesystems.php index 10c9d9b..e5708dc 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -15,6 +15,19 @@ return [ 'default' => env('FILESYSTEM_DRIVER', 'local'), + /* + |-------------------------------------------------------------------------- + | Default Cloud Filesystem Disk + |-------------------------------------------------------------------------- + | + | Many applications store files both locally and in the cloud. For this + | reason, you may specify a default "cloud" driver here. This driver + | will be bound as the Cloud disk implementation in the container. + | + */ + + 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + /* |-------------------------------------------------------------------------- | Filesystem Disks @@ -38,7 +51,7 @@ return [ 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), - 'url' => env('APP_URL').'/storage', + 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', ], @@ -46,8 +59,8 @@ return [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION'), - 'bucket' => env('AWS_BUCKET'), + 'region' => env('AWS_DEFAULT_REGION','us'), + 'bucket' => env('AWS_BUCKET','real-test-2'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), ], @@ -66,7 +79,7 @@ return [ */ 'links' => [ - public_path('storage') => storage_path('app/public'), + base_path('public/storage') => storage_path('app/public'), ], ]; diff --git a/config/geoip.php b/config/geoip.php new file mode 100644 index 0000000..62925df --- /dev/null +++ b/config/geoip.php @@ -0,0 +1,172 @@ + false, + + /* + |-------------------------------------------------------------------------- + | Include Currency in Results + |-------------------------------------------------------------------------- + | + | When enabled the system will do it's best in deciding the user's currency + | by matching their ISO code to a preset list of currencies. + | + */ + + 'include_currency' => false, + + /* + |-------------------------------------------------------------------------- + | Default Service + |-------------------------------------------------------------------------- + | + | Here you may specify the default storage driver that should be used + | by the framework. + | + | Supported: "maxmind_database", "maxmind_api", "ipapi" + | + */ + + 'service' => 'ipapi', + + /* + |-------------------------------------------------------------------------- + | Storage Specific Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure as many storage drivers as you wish. + | + */ + + 'services' => [ + + 'maxmind_database' => [ + 'class' => MaxMindDatabase::class, + 'database_path' => storage_path('app/geoip.mmdb'), + 'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')), + 'locales' => ['en'], + ], + + 'maxmind_api' => [ + 'class' => MaxMindWebService::class, + 'user_id' => env('MAXMIND_USER_ID'), + 'license_key' => env('MAXMIND_LICENSE_KEY'), + 'locales' => ['en'], + ], + + 'ipapi' => [ + 'class' => IPApi::class, + 'secure' => true, + 'key' => env('IPAPI_KEY'), + 'continent_path' => storage_path('app/continents.json'), + 'lang' => 'en', + ], + + 'ipgeolocation' => [ + 'class' => IPGeoLocation::class, + 'secure' => true, + 'key' => env('IPGEOLOCATION_KEY'), + 'continent_path' => storage_path('app/continents.json'), + 'lang' => 'en', + ], + + 'ipdata' => [ + 'class' => IPData::class, + 'key' => env('IPDATA_API_KEY'), + 'secure' => true, + ], + + 'ipfinder' => [ + 'class' => IPFinder::class, + 'key' => env('IPFINDER_API_KEY'), + 'secure' => true, + 'locales' => ['en'], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Default Cache Driver + |-------------------------------------------------------------------------- + | + | Here you may specify the type of caching that should be used + | by the package. + | + | Options: + | + | all - All location are cached + | some - Cache only the requesting user + | none - Disable cached + | + */ + + 'cache' => 'none', + + /* + |-------------------------------------------------------------------------- + | Cache Tags + |-------------------------------------------------------------------------- + | + | Cache tags are not supported when using the file or database cache + | drivers in Laravel. This is done so that only locations can be cleared. + | + */ + + 'cache_tags' => [], + + /* + |-------------------------------------------------------------------------- + | Cache Expiration + |-------------------------------------------------------------------------- + | + | Define how long cached location are valid. + | + */ + + 'cache_expires' => 30, + + /* + |-------------------------------------------------------------------------- + | Default Location + |-------------------------------------------------------------------------- + | + | Return when a location is not found. + | + */ + + 'default_location' => [ + 'ip' => '127.0.0.0', + 'iso_code' => 'IRN', + 'country' => 'Islamic Republic of Iran', + 'city' => 'Tehran', + 'state' => 'teh', + 'state_name' => 'Connecticut', + 'postal_code' => '513', + 'lat' => 35.6892, + 'lon' => 51.3890, + 'timezone' => 'Asia/Tehran', + 'continent' => 'Asia', + 'default' => true, + 'currency' => 'IRR', + ], + +]; diff --git a/config/logging.php b/config/logging.php index 6aa77fe..c6f3fe5 100644 --- a/config/logging.php +++ b/config/logging.php @@ -1,104 +1,44 @@ env('LOG_CHANNEL', 'stack'), - /* - |-------------------------------------------------------------------------- - | Log Channels - |-------------------------------------------------------------------------- - | - | Here you may configure the log channels for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Drivers: "single", "daily", "slack", "syslog", - | "errorlog", "monolog", - | "custom", "stack" - | - */ - 'channels' => [ + 'hi' => [ + 'driver' => 'custom', + 'via' => CreateCustomLogger::class, + 'handler' => AmqpHandler::class, + 'with' => [ + 'exchange' => new AMQPChannel( + new AMQPStreamConnection("liwo_rabbitmq_1", 5672, 'root', 'root') + ), + 'exchangeName' => 'log_exchange' + ], + ], + 'stack' => [ 'driver' => 'stack', - 'channels' => ['single'], + 'channels' => ['hi', 'daily',], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), + 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), + 'level' => 'debug', 'days' => 14, ], - - 'slack' => [ - 'driver' => 'slack', - 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => 'Laravel Log', - 'emoji' => ':boom:', - 'level' => env('LOG_LEVEL', 'critical'), - ], - - 'papertrail' => [ - 'driver' => 'monolog', - 'level' => env('LOG_LEVEL', 'debug'), - 'handler' => SyslogUdpHandler::class, - 'handler_with' => [ - 'host' => env('PAPERTRAIL_URL'), - 'port' => env('PAPERTRAIL_PORT'), - ], - ], - - 'stderr' => [ - 'driver' => 'monolog', - 'handler' => StreamHandler::class, - 'formatter' => env('LOG_STDERR_FORMATTER'), - 'with' => [ - 'stream' => 'php://stderr', - ], - ], - - 'syslog' => [ - 'driver' => 'syslog', - 'level' => env('LOG_LEVEL', 'debug'), - ], - - 'errorlog' => [ - 'driver' => 'errorlog', - 'level' => env('LOG_LEVEL', 'debug'), - ], - - 'null' => [ - 'driver' => 'monolog', - 'handler' => NullHandler::class, - ], - - 'emergency' => [ - 'path' => storage_path('logs/laravel.log'), - ], ], - ]; 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/config/media-library.php b/config/media-library.php new file mode 100644 index 0000000..6d21069 --- /dev/null +++ b/config/media-library.php @@ -0,0 +1,179 @@ + env('MEDIA_DISK', 'public'), + + /* + * The maximum file size of an item in bytes. + * Adding a larger file will result in an exception. + */ + 'max_file_size' => 1024 * 1024 * 10, + + /* + * This queue will be used to generate derived and responsive images. + * Leave empty to use the default queue. + */ + 'queue_name' => '', + + /* + * By default all conversions will be performed on a queue. + */ + 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true), + + /* + * The fully qualified class name of the media model. + */ + 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class, + + 'remote' => [ + /* + * Any extra headers that should be included when uploading media to + * a remote disk. Even though supported headers may vary between + * different drivers, a sensible default has been provided. + * + * Supported by S3: CacheControl, Expires, StorageClass, + * ServerSideEncryption, Metadata, ACL, ContentEncoding + */ + 'extra_headers' => [ + 'CacheControl' => 'max-age=604800', + ], + ], + + 'responsive_images' => [ + + /* + * This class is responsible for calculating the target widths of the responsive + * images. By default we optimize for filesize and create variations that each are 20% + * smaller than the previous one. More info in the documentation. + * + * https://docs.spatie.be/laravel-medialibrary/v8/advanced-usage/generating-responsive-images + */ + 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class, + + /* + * By default rendering media to a responsive image will add some javascript and a tiny placeholder. + * This ensures that the browser can already determine the correct layout. + */ + 'use_tiny_placeholders' => true, + + /* + * This class will generate the tiny placeholder used for progressive image loading. By default + * the media library will use a tiny blurred jpg image. + */ + 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, + ], + + /* + * When converting Media instances to response the media library will add + * a `loading` attribute to the `img` tag. Here you can set the default + * value of that attribute. + * + * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction. + * + * More info: https://css-tricks.com/native-lazy-loading/ + */ + 'default_loading_attribute_value' => null, + + /* + * This is the class that is responsible for naming conversion files. By default, + * it will use the filename of the original and concatenate the conversion name to it. + */ + 'conversion_file_namer' => App\Utilities\Avatar\DefaultConversionFileNamer::class, + + /* + * The class that contains the strategy for determining a media file's path. + */ + 'path_generator' => App\Utilities\Avatar\DefaultPathGenerator::class, + + /* + * When urls to files get generated, this class will be called. Use the default + * if your files are stored locally above the site root or on s3. + */ + 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class, + + /* + * Whether to activate versioning when urls to files get generated. + * When activated, this attaches a ?v=xx query string to the URL. + */ + 'version_urls' => false, + + /* + * The media library will try to optimize all converted images by removing + * metadata and applying a little bit of compression. These are + * the optimizers that will be used by default. + */ + 'image_optimizers' => [ + Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ + '--strip-all', // this strips out all text information such as comments and EXIF data + '--all-progressive', // this will make sure the resulting image is a progressive one + ], + Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ + '--force', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Optipng::class => [ + '-i0', // this will result in a non-interlaced, progressive scanned image + '-o2', // this set the optimization level to two (multiple IDAT compression trials) + '-quiet', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Svgo::class => [ + '--disable=cleanupIDs', // disabling because it is known to cause troubles + ], + Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ + '-b', // required parameter for this package + '-O3', // this produces the slowest but best results + ], + ], + + /* + * These generators will be used to create an image of media files. + */ + 'image_generators' => [ + Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class, + ], + + /* + * The engine that should perform the image conversions. + * Should be either `gd` or `imagick`. + */ + 'image_driver' => env('IMAGE_DRIVER', 'gd'), + + /* + * FFMPEG & FFProbe binaries paths, only used if you try to generate video + * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer + * dependency. + */ + 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'), + 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'), + + /* + * The path where to store temporary files while performing image conversions. + * If set to null, storage_path('media-library/temp') will be used. + */ + 'temporary_directory_path' => null, + + /* + * Here you can override the class names of the jobs used by this package. Make sure + * your custom jobs extend the ones provided by the package. + */ + 'jobs' => [ + 'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class, + 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class, + ], + + /* + * When using the addMediaFromUrl method you may want to replace the default downloader. + * This is particularly useful when the url of the image is behind a firewall and + * need to add additional flags, possibly using curl. + */ + 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class, + +]; diff --git a/config/query-builder.php b/config/query-builder.php new file mode 100644 index 0000000..b65680f --- /dev/null +++ b/config/query-builder.php @@ -0,0 +1,38 @@ + [ + 'include' => 'include', + + 'filter' => 'filter', + + 'sort' => 'sort', + + 'fields' => 'fields', + + 'append' => 'append', + ], + + /* + * Related model counts are included using the relationship name suffixed with this string. + * For example: GET /users?include=postsCount + */ + 'count_suffix' => 'Count', + + /* + * By default the package will throw an `InvalidFilterQuery` exception when a filter in the + * URL is not allowed in the `allowedFilters()` method. + */ + 'disable_invalid_filter_query_exception' => false, + +]; diff --git a/config/services.php b/config/services.php index 2a1d616..5c0ae34 100644 --- a/config/services.php +++ b/config/services.php @@ -1,33 +1,21 @@ [ - 'domain' => env('MAILGUN_DOMAIN'), - 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'task' => 'hi-task-app', + 'zarinpal' => [ + 'merchant-id' => '68d337b0-4b77-11ea-8409-000c295eb8fc', + 'type' => 'zarin-gate', // Types: [zarin-gate || normal] + 'callback-url' => 'http://127.0.0.1:8000/user/v1/callback', + 'server' => 'germany', // Servers: [germany || iran || test] + 'email' => 'admin@base.com', + 'mobile' => '09123456789', + 'description' => env('APP_NAME', 'APP_NAME'), + 'sandbox' => true, ], - 'postmark' => [ - 'token' => env('POSTMARK_TOKEN'), + 'google' => [ + 'client_id' => '1002439248397-oa6hnh25n6qri3q4kst62gvb1k9ki65l.apps.googleusercontent.com', + 'client_secret' => 'tKbiyh5hOjYIcj-W1y3N8X5R', + 'redirect' => env('APP_URL').'/user/v1/auth/google/callback', ], - - 'ses' => [ - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - ], - ]; diff --git a/database/factories/BusinessFactory.php b/database/factories/BusinessFactory.php new file mode 100644 index 0000000..28f0dae --- /dev/null +++ b/database/factories/BusinessFactory.php @@ -0,0 +1,17 @@ +define(Business::class, function (Faker $faker) { + return [ + 'name' => $name = $faker->unique()->company, + 'slug' => Str::slug($name) . $faker->numberBetween(1, 100), + 'wallet' => random_int(111111, 999999), + 'color' => $faker->colorName, + 'calculated_at' => \Carbon\Carbon::now()->subDays(random_int(1, 31)), + ]; +}); diff --git a/database/factories/CostFactory.php b/database/factories/CostFactory.php new file mode 100644 index 0000000..6e84128 --- /dev/null +++ b/database/factories/CostFactory.php @@ -0,0 +1,19 @@ +define(Cost::class, function (Faker $faker) { + return [ + 'business_id' => random_int(1,5000), + 'type' => $type = $faker->boolean() ? 'users' : 'files', + 'month' => jdate(Carbon::now()->subDays(random_int(0,90)))->format("Y-m-01"), + 'amount' => random_int(1,1000), + 'fee' => $type === 'users' ? enum("business.fee.user") : enum("business.fee.file"), + 'duration' => random_int(1,60), + 'created_at' => Carbon::now()->subMinutes(random_int(1, 1000)), + ]; +}); diff --git a/database/factories/FileFactory.php b/database/factories/FileFactory.php new file mode 100644 index 0000000..45191da --- /dev/null +++ b/database/factories/FileFactory.php @@ -0,0 +1,35 @@ +define(File::class, function (Faker $faker) { + $mimes = ['application/pdf', 'video/mp4', 'image/png', 'image/jpeg', 'audio/x-wav']; + $extensions = ['pdf', 'mp4', 'png', 'jpg', 'wav']; + $groups = [ + 'pdf' => 'pdf', + 'mp4' => 'video', + 'png' => 'image', + 'jpg' => 'image', + 'wav' => 'audio', + ]; + $sizes = [1, 5, 128, 256, 1024, 2048]; + $rand_type = $faker->numberBetween(0, 4); + return [ + 'user_id' => $faker->numberBetween(1, 200), + 'business_id' => $faker->numberBetween(1, 200), + 'project_id' => $faker->numberBetween(1, 200), + 'attached_to_id' => $faker->numberBetween(1, 100), + 'attached_to_table' => enum('tables.tasks.id'), + 'disk' => 's3', + 'original_name' => $faker->words(1, true), + 'name' => $faker->words(1, true), + 'extension' => $extension = $extensions[$rand_type], + 'mime' => $mimes[$rand_type], + 'group' => $groups[$extension], + 'size' => $sizes[$faker->numberBetween(0, 5)], + 'description' => $faker->text, + ]; +}); diff --git a/database/factories/FingerprintFactory.php b/database/factories/FingerprintFactory.php new file mode 100644 index 0000000..d8458e0 --- /dev/null +++ b/database/factories/FingerprintFactory.php @@ -0,0 +1,37 @@ +define(Fingerprint::class, function () use ($faker) { + $os = [ + $faker->windowsPlatformToken, + $faker->linuxPlatformToken, + $faker->macPlatformToken + ]; + + $browsers = [ + $faker->firefox, + $faker->chrome, + $faker->opera, + $faker->safari, + ]; + + $detector = new Jenssegers\Agent\Agent(); + + return [ + 'user_id' => $faker->numberBetween(1, 1000), + 'agent' => $detector->browser(Arr::random($browsers)), + 'ip' => $faker->ipv4, + 'os' => $detector->platform(Arr::random($browsers)), + 'latitude' => $faker->latitude, + 'longitude' => $faker->longitude, + 'token' => Str::random(60), + ]; +}); diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php new file mode 100644 index 0000000..d7dce12 --- /dev/null +++ b/database/factories/ProjectFactory.php @@ -0,0 +1,22 @@ +define(Project::class, function (Faker $faker) { + return [ + 'business_id' => null, + 'name' => $name = $faker->words(3, true), + 'slug' => Str::slug($name), + 'private' => false, + 'budget' => 0, + 'start' => null, + 'finish' => null, + 'color' => $faker->colorName, + 'active' => rand(0, 1), + 'description' => $faker->paragraph, + ]; +}); diff --git a/database/factories/SprintflowFactory.php b/database/factories/SprintflowFactory.php new file mode 100644 index 0000000..b5c3031 --- /dev/null +++ b/database/factories/SprintflowFactory.php @@ -0,0 +1,16 @@ +define(Sprint::class, function (Faker $faker) { + return [ + 'business_id' => null, + 'name' => $faker->randomElement(['scrum', 'printing', + 'agile', 'develop', 'design', 'writing', 'seo', 'sale']), + 'active' => rand(0, 1), + 'description' => $faker->paragraph, + ]; +}); diff --git a/database/factories/SystemFactory.php b/database/factories/SystemFactory.php new file mode 100644 index 0000000..0631afb --- /dev/null +++ b/database/factories/SystemFactory.php @@ -0,0 +1,14 @@ +define(System::class, function (Faker $faker) { + return [ + 'business_id' => null, + 'project_id' => null, + 'name' => $name = $faker->words(3, true), + ]; +}); diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php new file mode 100644 index 0000000..5941665 --- /dev/null +++ b/database/factories/TagFactory.php @@ -0,0 +1,15 @@ +define(Tag::class, function () use ($faker) { + return [ + 'label' => $faker->colorName, + 'color' => $faker->colorName, + 'business_id' => null, + ]; +}); diff --git a/database/factories/TaskFactory.php b/database/factories/TaskFactory.php new file mode 100644 index 0000000..66c399a --- /dev/null +++ b/database/factories/TaskFactory.php @@ -0,0 +1,32 @@ +define(Task::class, function (Faker $faker) { + return [ + 'business_id' => $faker->numberBetween(1, 200), + 'creator_id' => $faker->numberBetween(1, 5000), + 'project_id' => $faker->numberBetween(1, 200), + 'assignee_id' => $faker->numberBetween(1, 20), + 'workflow_id' => $faker->numberBetween(1, 2500), + 'system_id' => $faker->numberBetween(1, 20), + 'sprint_id' => $faker->numberBetween(1, 20), + 'status_id' => $faker->numberBetween(1, 5000), + 'approver_id' => $faker->numberBetween(1, 5000), + + 'title' => $faker->name, + 'description' => $faker->sentences(3, true), + 'priority' => $faker->numberBetween(1,10), + 'on_time' => $faker->boolean, + 'ready_to_test' => $faker->boolean, + 'spent_time' => $faker->numberBetween(100, 300), + 'estimated_time' => $faker->numberBetween(100, 300), + 'due_date' => $faker->date(), + 'completed_at' => $faker->date(), + 'work_start' => $faker->date('1971-01-01','now'), + 'work_finish' => $faker->date('1971-01-01','now'), + ]; +}); diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php new file mode 100644 index 0000000..80a58ff --- /dev/null +++ b/database/factories/TransactionFactory.php @@ -0,0 +1,21 @@ +define(Transaction::class, function (Faker $faker) { + $business_user = DB::selectOne('select business_id, user_id from business_user where level = 4 order by rand() limit 1'); + + return [ + 'user_id' => $business_user->user_id, + 'business_id' => $business_user->business_id, + 'amount' => random_int(1000, 9999), + 'succeeded' => $faker->boolean(), + 'options' => json_encode([]), + 'created_at' => Carbon::now()->subMinutes(random_int(1, 1000)), + ]; +}); diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 3510ed6..779f795 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -1,47 +1,16 @@ $this->faker->name, - 'email' => $this->faker->unique()->safeEmail, - 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password - 'remember_token' => Str::random(10), - ]; - } - - /** - * Indicate that the model's email address should be unverified. - * - * @return \Illuminate\Database\Eloquent\Factories\Factory - */ - public function unverified() - { - return $this->state(function (array $attributes) { - return [ - 'email_verified_at' => null, - ]; - }); - } -} +$factory->define(User::class, function (Faker $faker) { + return [ + 'name' => $faker->name, + 'email' => $faker->unique()->safeEmail, + 'mobile' => $faker->unique()->phoneNumber, + 'username' => $faker->unique()->userName, + 'password' => '$2y$10$l8jgLtb7RyDd7wbvxYPsuu7gjo/bLBkBYQhXnkpdmm.SVF3CT00UW', + ]; +}); diff --git a/database/factories/WorkFactory.php b/database/factories/WorkFactory.php new file mode 100644 index 0000000..f593e38 --- /dev/null +++ b/database/factories/WorkFactory.php @@ -0,0 +1,22 @@ +define(Work::class, function (Faker $faker) { + $started_at = $faker->dateTime('now'); + $ended_at = $faker->dateTime('now'); +// $minute_sum = $ended_at->diffInMinutes($started_at); + return [ + 'business_id' => $faker->numberBetween(1, 200), + 'project_id' => $faker->numberBetween(1, 200), + 'task_id' => $faker->numberBetween(1, 200), + 'user_id' => $faker->numberBetween(1, 5000), + 'message' => $faker->name, + 'minute_sum' => $faker->numberBetween(100, 5000), + 'started_at' => $started_at, + 'ended_at' => $ended_at, + ]; +}); diff --git a/database/factories/WorkflowFactory.php b/database/factories/WorkflowFactory.php new file mode 100644 index 0000000..4d51b1c --- /dev/null +++ b/database/factories/WorkflowFactory.php @@ -0,0 +1,15 @@ +define(Workflow::class, function (Faker $faker) { + return [ + 'business_id' => null, + 'name' => $faker->randomElement(['scrum', 'printing', + 'agile', 'develop', 'design', 'writing', 'seo', 'sale']), + 'desc' => $faker->sentences(1, true), + ]; +}); diff --git a/database/factories/WorkstatusFactory.php b/database/factories/WorkstatusFactory.php new file mode 100644 index 0000000..0c5f1fa --- /dev/null +++ b/database/factories/WorkstatusFactory.php @@ -0,0 +1,15 @@ +define(Status::class, function (Faker $faker) { + return [ + 'name' => $faker->randomElement(['idea', 'todo', + 'doing', 'done', 'pending', 'hold', 'logestik', 'sew']), + 'state' => rand(0, 3), + 'order' => rand(0, 10), + ]; +}); diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2020_08_18_085016_create_users_table.php similarity index 62% rename from database/migrations/2014_10_12_000000_create_users_table.php rename to database/migrations/2020_08_18_085016_create_users_table.php index 621a24e..b656a71 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2020_08_18_085016_create_users_table.php @@ -17,10 +17,14 @@ class CreateUsersTable extends Migration $table->id(); $table->string('name'); $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); + $table->string('mobile')->unique()->nullable(); + $table->string('username')->unique(); $table->string('password'); - $table->rememberToken(); - $table->timestamps(); + $table->boolean('active')->default(false); + $table->boolean('has_avatar')->default(false); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + $table->timestamp('deleted_at')->nullable(); }); } diff --git a/database/migrations/2020_08_18_085017_fingerprints.php b/database/migrations/2020_08_18_085017_fingerprints.php new file mode 100644 index 0000000..6a72833 --- /dev/null +++ b/database/migrations/2020_08_18_085017_fingerprints.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('agent'); + $table->ipAddress('ip'); + $table->string('os'); + $table->decimal('latitude', 10, 4); + $table->decimal('longitude', 11, 4); + $table->char('token', 60)->unique(); + $table->text('fcm_token')->unique()->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('fingerprints'); + } +} diff --git a/database/migrations/2020_08_18_085018_create_businesses_table.php b/database/migrations/2020_08_18_085018_create_businesses_table.php new file mode 100644 index 0000000..b0d168f --- /dev/null +++ b/database/migrations/2020_08_18_085018_create_businesses_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->integer('wallet')->default(0); + $table->unsignedInteger('files_volume')->default(0); + $table->string('color')->nullable(); + $table->string('description')->nullable(); + $table->json('cache')->nullable(); + $table->boolean('has_avatar')->default(false); + $table->timestamp('calculated_at')->nullable()->index(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->nullable(); + $table->timestamp('deleted_at')->nullable(); + }); + + Schema::create('business_user', function (Blueprint $table) { + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('user_id'); + $table->tinyInteger('level')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('businesses'); + Schema::dropIfExists('businesses_user'); + } +} diff --git a/database/migrations/2020_08_18_085046_create_projects_table.php b/database/migrations/2020_08_18_085046_create_projects_table.php new file mode 100644 index 0000000..4b40f42 --- /dev/null +++ b/database/migrations/2020_08_18_085046_create_projects_table.php @@ -0,0 +1,58 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->string('name'); + $table->string('slug'); + $table->boolean('private')->default(false); + $table->unsignedBigInteger('budget')->default(0); + $table->string('color')->nullable(); + $table->boolean('active')->nullable(); + $table->text('description')->nullable(); + $table->boolean('has_avatar')->default(false); + + $table->timestamp('start')->nullable(); + $table->timestamp('finish')->nullable(); + + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + $table->timestamp('deleted_at')->nullable(); + + }); + + // Only those users that added directly to the project are stored here. + // Users who are members of the business have access to the projects of that business + // without being stored here. + Schema::create('project_user', function (Blueprint $table) { + $table->unsignedBigInteger('project_id'); + $table->unsignedBigInteger('user_id'); + $table->tinyInteger('level')->default(0); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('projects'); + Schema::dropIfExists('project_user'); + } +} diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2020_08_18_085054_create_workflows_table.php similarity index 53% rename from database/migrations/2014_10_12_100000_create_password_resets_table.php rename to database/migrations/2020_08_18_085054_create_workflows_table.php index 0ee0a36..d998c40 100644 --- a/database/migrations/2014_10_12_100000_create_password_resets_table.php +++ b/database/migrations/2020_08_18_085054_create_workflows_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreatePasswordResetsTable extends Migration +class CreateWorkflowsTable extends Migration { /** * Run the migrations. @@ -13,10 +13,13 @@ class CreatePasswordResetsTable extends Migration */ public function up() { - Schema::create('password_resets', function (Blueprint $table) { - $table->string('email')->index(); - $table->string('token'); + Schema::create('workflows', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('business_id'); + $table->string('name'); + $table->string('desc')->nullable(); $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); }); } @@ -27,6 +30,6 @@ class CreatePasswordResetsTable extends Migration */ public function down() { - Schema::dropIfExists('password_resets'); + Schema::dropIfExists('workflows'); } } diff --git a/database/migrations/2020_08_18_085102_create_statuses_table.php b/database/migrations/2020_08_18_085102_create_statuses_table.php new file mode 100644 index 0000000..03c4203 --- /dev/null +++ b/database/migrations/2020_08_18_085102_create_statuses_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('workflow_id'); + $table->string('name'); + $table->string('state')->default(enum('status.states.inactive.id')); + $table->unsignedSmallInteger('order')->default(0); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('statuses'); +// Schema::dropIfExists('status_workflow'); + } +} diff --git a/database/migrations/2020_08_18_085114_create_tags_table.php b/database/migrations/2020_08_18_085114_create_tags_table.php new file mode 100644 index 0000000..6c00987 --- /dev/null +++ b/database/migrations/2020_08_18_085114_create_tags_table.php @@ -0,0 +1,43 @@ +id(); + $table->string('label'); + $table->string('color')->nullable(); + $table->unsignedBigInteger('business_id'); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + }); + + Schema::create('tag_task', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('tag_id'); + $table->unsignedBigInteger('task_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('tags'); + Schema::dropIfExists('tag_task'); + } +} diff --git a/database/migrations/2020_08_28_095802_create_systems_table.php b/database/migrations/2020_08_28_095802_create_systems_table.php new file mode 100644 index 0000000..32fbb14 --- /dev/null +++ b/database/migrations/2020_08_28_095802_create_systems_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('systems'); + } +} diff --git a/database/migrations/2020_08_28_101545_create_sprint_table.php b/database/migrations/2020_08_28_101545_create_sprint_table.php new file mode 100644 index 0000000..9a73d99 --- /dev/null +++ b/database/migrations/2020_08_28_101545_create_sprint_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->string('name'); + $table->text('description')->nullable(); + $table->date('started_at'); + $table->date('ended_at'); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('sprints'); + } +} diff --git a/database/migrations/2020_10_31_182018_create_transactions_table.php b/database/migrations/2020_10_31_182018_create_transactions_table.php new file mode 100644 index 0000000..5fa18e5 --- /dev/null +++ b/database/migrations/2020_10_31_182018_create_transactions_table.php @@ -0,0 +1,37 @@ +bigInteger('id', true, true); + $table->bigInteger('user_id', false, true); + $table->bigInteger('business_id', false, true); + $table->integer('amount', false, true); + $table->boolean('succeeded')->default(false); + $table->json('options')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('transactions'); + } +} diff --git a/database/migrations/2021_03_02_082607_create_tasks_table.php b/database/migrations/2021_03_02_082607_create_tasks_table.php new file mode 100644 index 0000000..0ce9c7f --- /dev/null +++ b/database/migrations/2021_03_02_082607_create_tasks_table.php @@ -0,0 +1,55 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->unsignedBigInteger('creator_id'); + $table->unsignedBigInteger('assignee_id')->nullable(); + $table->unsignedBigInteger('system_id')->nullable(); + $table->unsignedBigInteger('sprint_id')->nullable(); + $table->unsignedBigInteger('workflow_id'); + $table->unsignedBigInteger('status_id'); + $table->unsignedBigInteger('approver_id')->nullable(); + + $table->string('title'); + $table->text('description')->nullable(); + $table->integer('priority')->default(1); + $table->boolean('on_time')->default(true); + $table->boolean('ready_to_test')->default(false); + + $table->json('watchers')->nullable(); + + $table->integer('spent_time')->default(0); + $table->integer('estimated_time')->default(0); + $table->date('due_date')->nullable(); + $table->date('completed_at')->nullable(); + $table->timestamp('work_start')->nullable(); + $table->timestamp('work_finish')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('tasks'); + } +} diff --git a/database/migrations/2021_03_02_085913_create_works_table.php b/database/migrations/2021_03_02_085913_create_works_table.php new file mode 100644 index 0000000..4274ff8 --- /dev/null +++ b/database/migrations/2021_03_02_085913_create_works_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->unsignedBigInteger('task_id'); + $table->unsignedBigInteger('user_id'); + $table->string('message')->nullable(); + $table->integer('minute_sum')->default(0); + $table->dateTime('started_at'); + $table->dateTime('ended_at'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('works'); + } +} diff --git a/database/migrations/2021_03_02_092444_create_comments_table.php b/database/migrations/2021_03_02_092444_create_comments_table.php new file mode 100644 index 0000000..c653af3 --- /dev/null +++ b/database/migrations/2021_03_02_092444_create_comments_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->unsignedBigInteger('task_id'); + $table->unsignedBigInteger('user_id'); + $table->text('body'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('comments'); + } +} diff --git a/database/migrations/2021_03_03_093326_create_media_table.php b/database/migrations/2021_03_03_093326_create_media_table.php new file mode 100644 index 0000000..378b046 --- /dev/null +++ b/database/migrations/2021_03_03_093326_create_media_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + + $table->morphs('model'); + $table->uuid('uuid')->nullable()->unique(); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->string('conversions_disk')->nullable(); + $table->unsignedBigInteger('size'); + $table->json('manipulations'); + $table->json('custom_properties'); + $table->json('generated_conversions'); + $table->json('responsive_images'); + $table->unsignedInteger('order_column')->nullable(); + + $table->nullableTimestamps(); + }); + } +} diff --git a/database/migrations/2021_03_06_085855_create_activities_table.php b/database/migrations/2021_03_06_085855_create_activities_table.php new file mode 100644 index 0000000..ee00afe --- /dev/null +++ b/database/migrations/2021_03_06_085855_create_activities_table.php @@ -0,0 +1,45 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id')->nullable(); + $table->unsignedBigInteger('system_id')->nullable(); + $table->unsignedBigInteger('workflow_id')->nullable(); + $table->unsignedBigInteger('status_id')->nullable(); + $table->unsignedBigInteger('sprint_id')->nullable(); + $table->unsignedBigInteger('task_id')->nullable(); + $table->unsignedBigInteger('subject_id')->nullable();//row id + $table->unsignedBigInteger('actor_id'); + $table->unsignedBigInteger('user_id')->nullable(); + $table->unsignedBigInteger('crud_id')->nullable(); + $table->unsignedBigInteger('table_id')->nullable(); + $table->json('original')->nullable(); // a unique identifier that represent the type of action + $table->json('diff')->nullable(); // all data that has been changed + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('activities'); + } +} diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2021_03_06_114918_create_failed_jobs_table.php similarity index 100% rename from database/migrations/2019_08_19_000000_create_failed_jobs_table.php rename to database/migrations/2021_03_06_114918_create_failed_jobs_table.php 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/database/migrations/2021_09_03_085114_create_costs_table.php b/database/migrations/2021_09_03_085114_create_costs_table.php new file mode 100644 index 0000000..87768ec --- /dev/null +++ b/database/migrations/2021_09_03_085114_create_costs_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('business_id'); + $table->string('type'); + $table->date('month'); + $table->unsignedInteger('amount'); + $table->unsignedInteger('fee')->default(0); + $table->unsignedInteger('duration'); + $table->unsignedInteger('cost')->storedAs('fee*duration'); + $table->unsignedFloat('tax', 8, 2)->storedAs('(cost/100)*9'); + $table->json('additional')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('costs'); + } +} diff --git a/database/migrations/2022_10_13_085114_create_files_table.php b/database/migrations/2022_10_13_085114_create_files_table.php new file mode 100644 index 0000000..69dfec8 --- /dev/null +++ b/database/migrations/2022_10_13_085114_create_files_table.php @@ -0,0 +1,45 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('business_id'); + $table->unsignedBigInteger('project_id'); + $table->unsignedBigInteger('attached_to_id')->nullable(); + $table->integer('attached_to_table')->nullable(); + $table->char('disk',191); + $table->char('original_name',191); + $table->char('name',191); + $table->char('extension',4); + $table->string('mime'); + $table->string('group',255); + $table->char('size',191); + $table->text('description')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('files'); + } +} diff --git a/database/migrations/2023_10_13_085115_create_files_relation_table.php b/database/migrations/2023_10_13_085115_create_files_relation_table.php new file mode 100644 index 0000000..8308d77 --- /dev/null +++ b/database/migrations/2023_10_13_085115_create_files_relation_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('file_id'); + $table->unsignedBigInteger("resource_id"); + $table->string("resource_type"); + // the person who create this relation. eg attacher + $table->unsignedBigInteger('user_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('files_relation'); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php deleted file mode 100644 index 57b73b5..0000000 --- a/database/seeders/DatabaseSeeder.php +++ /dev/null @@ -1,18 +0,0 @@ -create(); - } -} diff --git a/database/seeds/BusinessSeeder.php b/database/seeds/BusinessSeeder.php new file mode 100644 index 0000000..1f39038 --- /dev/null +++ b/database/seeds/BusinessSeeder.php @@ -0,0 +1,144 @@ +raw()); + $projects = []; + $systems = []; + $business_user = []; + $project_user = []; + $project_id = 0; + for ($business_id=1; $business_id <= 2000; $business_id++) { + // distinct user ids + $user_ids = []; + $added_user = []; + for ($i=0; $i < 10; $i++) $user_ids [] = rand(1, 5000); + $user_ids = array_values(array_unique($user_ids)); + // add 1 to 7 member + for ($i=0; $i < rand(1, 8); $i++) { + $added_user[] = $user_ids[$i]; + $business_user[] = [ + 'business_id' => $business_id, + 'user_id' => $user_ids[$i], + 'level' => $i == 0? 4: rand(0, 4), + ]; + } + + $business_projects = factory(Project::class, rand(1, 3))->raw(); + foreach ($business_projects as $project) { + $project['business_id'] = $business_id; + $projects[] = $project; + $project_id++; + + for ($i=0; $i < rand(0, 2); $i++) { + if ($added_user[$i] ?? false) { + $project_user[] = [ + 'project_id' => $project_id, + 'user_id' => $added_user[$i], + 'level' => rand(0, 4), + ]; + } + } + + for ($i=0; $i < rand(1, 5); $i++) { + $systems[] = factory(System::class)->raw([ + 'project_id' => $project_id, + 'business_id' => $business_id, + ]); + } + } + + // for ($i=0; $i < rand(1, 3); $i++) { + // // $project = + // } + // $business_user[] = [ + // 'business_id' => $business_id, + // 'user_id' => 1, + // 'owner' => true, + // ]; + } + DB::table('business_user')->insert($business_user); + Project::insert($projects); + DB::table('project_user')->insert($project_user); + DB::table('systems')->insert($systems); + /** + * Every business has one or more owners, which might be the owner of other business as well + */ + // $this->insertAdmins($business_ids, $owners); + + + /** + * Every business has one or more members, which might be the member of other business as well + */ + // $this->insertMembers($business_ids); + } + + /** + * @param $business_ids + * @param Collection $owners + * @return array + */ + public function insertAdmins($business_ids, Collection $owners) + { + $relations = []; + foreach ($business_ids as $id) { + $relations[] = [ + 'business_id' => $id, + 'user_id' => $owners->random()->id, + 'owner' => true, + ]; + } + + // Specify the percentage of managers who have two or more businesses + $multiple = ceil(count($relations) / 100 * 30); + foreach (Arr::random($relations, $multiple) as $relation) { + $relations[] = [ + 'business_id' => $relation['business_id'], + 'user_id' => $owners->except($relation['user_id'])->random()->id, + 'owner' => true, + ]; + } + + DB::table('business_user')->insert($relations); + } + + /** + * @param $business_ids + */ + public function insertMembers($business_ids): void + { + /** @var Collection $member_ids */ + $member_ids = User::whereDoesntHave('businesses')->select('id')->pluck('id'); + $relations = []; + $current = []; + foreach ($member_ids as $index => $id) { + $relations[] = [ + 'business_id' => $business = $business_ids->random(), + 'user_id' => $id, + ]; + + $current[$business][] = $index; + } + + $multiple = ceil(count($relations) / 100 * 20); + foreach (Arr::random($relations, $multiple) as $relation) { + $relations[] = [ + 'business_id' => $relation['business_id'], + 'user_id' => $member_ids->except($current[$relation['business_id']])->random(), + ]; + } + + DB::table('business_user')->insert($relations); + } +} diff --git a/database/seeds/CostSeeder.php b/database/seeds/CostSeeder.php new file mode 100644 index 0000000..ca76bec --- /dev/null +++ b/database/seeds/CostSeeder.php @@ -0,0 +1,14 @@ +raw() + ); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php new file mode 100644 index 0000000..7508ea3 --- /dev/null +++ b/database/seeds/DatabaseSeeder.php @@ -0,0 +1,22 @@ +call([ + UserSeeder::class, + BusinessSeeder::class, + TagSeeder::class, + WorkflowSeeder::class, + SprintSeeder::class, +// TransactionSeeder::class, +// CostSeeder::class, + TaskTmpSeeder::class, + // ProjectSeeder::class, + // TaskSeeder::class, + ]); + } +} diff --git a/database/seeds/ProjectSeeder.php b/database/seeds/ProjectSeeder.php new file mode 100644 index 0000000..70e0bec --- /dev/null +++ b/database/seeds/ProjectSeeder.php @@ -0,0 +1,123 @@ +pluck('id'); + + /** + * One level projects + */ + $this->insertMainProjects($businesses); + + /** + * Multi level projects + * Be careful with private projects + */ + $this->insertChildProjects(); + + $this->insertGrandChildrenProjects(); + + /** + * Assign people to the projects + * * Be careful with private projects + */ + $this->assignPeopleToProjects(); + } + + /** + * @param $businesses + * @return void + */ + public function insertMainProjects($businesses): void + { + $projects = []; + foreach ($businesses as $business) { + $projects[] = factory(Project::class, 5)->raw([ + 'business_id' => $business, + 'private' => rand(1, 100) <= 10, + ]); + } + DB::table('projects')->insertOrIgnore(Arr::collapse($projects)); + } + + /** + * @return void + */ + public function insertChildProjects(): void + { + $projects = Project::inRandomOrder()->select('id', 'business_id')->take(100)->get()->toArray(); + $child_projects = []; + foreach ($projects as $project) { + $child_projects[] = factory(Project::class)->raw([ + 'parent_id' => $project['id'], + 'business_id' => $project['business_id'], + 'private' => rand(1, 100) <= 10, + ]); + } + DB::table('projects')->insert($child_projects); + } + + public function insertGrandChildrenProjects(): void + { + $projects = Project::inRandomOrder() + ->select('id', 'business_id', 'parent_id') + ->whereNotNull('parent_id') + ->take(25)->get()->toArray(); + $grand_child_projects = []; + foreach ($projects as $project) { + $grand_child_projects[] = factory(Project::class)->raw([ + 'parent_id' => $project['id'], + 'business_id' => $project['business_id'], + 'private' => rand(1, 100) <= 10, + ]); + } + DB::table('projects')->insertOrIgnore($grand_child_projects); + } + + public function assignPeopleToProjects(): void + { + // Schema::create('') + // $table->unsignedBigInteger('project_id'); + // $table->unsignedBigInteger(''); + + $project_members = []; + $projects = Project::with('parent','children','business')->get(); + foreach ($projects as $project) { + // detect the main projects + if ($project->children->isNotEmpty()) { + continue; + } + + $business_owner = $project->business->owners->random(); + $business_members = $project->business->members->random($project->business->members->count() - 1); + $project_members[] = [ + 'project_id' => $project->id, + 'user_id' => $business_owner->id, + ]; + + foreach ($business_members as $business_member) { + $project_members[] = [ + 'project_id' => $project->id, + 'user_id' => $business_member->id, + ]; + } + + // todo : how to detect a project has sub project + // child + // grand child + } + DB::table('project_user')->insert($project_members); + } +} diff --git a/database/seeds/SprintSeeder.php b/database/seeds/SprintSeeder.php new file mode 100644 index 0000000..1bbf262 --- /dev/null +++ b/database/seeds/SprintSeeder.php @@ -0,0 +1,21 @@ +raw([ + 'business_id' => $business_id, + 'project_id' => rand(1, 4027), + ]); + } + } + DB::table('sprints')->insertOrIgnore($sprints); + } +} diff --git a/database/seeds/TagSeeder.php b/database/seeds/TagSeeder.php new file mode 100644 index 0000000..e9eff1f --- /dev/null +++ b/database/seeds/TagSeeder.php @@ -0,0 +1,21 @@ +raw([ + 'business_id' => $business_id, + ]); + } + } + DB::table('tags')->insertOrIgnore($tags); + } +} diff --git a/database/seeds/TaskSeeder.php b/database/seeds/TaskSeeder.php new file mode 100755 index 0000000..cc746e6 --- /dev/null +++ b/database/seeds/TaskSeeder.php @@ -0,0 +1,174 @@ +raw() + ); + } + + $tags = [ + 1 => 'باربری', + 2 => 'زیر چاپ', + 3 => 'برون سپاری شود', + 4 => 'مشتری نپسندید', + ]; + + $ids = Task::select('id')->pluck('id')->toArray(); + $tag_task_relation = []; + foreach ($ids as $id) { + foreach (array_rand($tags, mt_rand(2, 4)) as $index => $tag) { + $tag_task_relation[] = [ + 'task_id' => $id, + 'tag_id' => $tag, + ]; + } + } + + TagTask::insert($tag_task_relation); + $this->createRelatedData(); + } + + public function createRelatedData() + { + Task::whereIn('business_id', [1, 2, 3, 4, 5]) + ->update(['business_id' => 1779, 'project_id' => 0, 'workflow_id' => 0, 'status_id' => 0, + 'system_id' => 0, 'sprint_id' => 0]); + + $count = Task::where('business_id', '=', 1779)->count(); + + $projects = [ + 3566 => [ + 'systems' => [8967, 8968, 8969, 8970], + 'sprints' => [], + ], + 3567 => [ + 'systems' => [8971], + 'sprints' => [526] + ], + 3568 => [ + 'systems' => [8972, 8973], + 'sprints' => [5480] + ] + ]; + + $workflows = [ + 2277 => [5100, 5101], + 2278 => [5102, 5103], + 2279 => [5104, 5105, 5106, 5107] + ]; + $take = $count > 50 ? 20 : 10; + $this->updateField(array_keys($projects), $count, $take, 'project_id'); + $this->updateField(array_keys($workflows), $count, $take, 'workflow_id'); + $this->fillSprintAndSystem($projects); + $this->fillStatues($workflows); + $this->fillTaskTag($count); + + } + + public function fillSprintAndSystem($projects) + { + foreach ($projects as $key => $value) { + $count = Task::where('project_id', $key)->count(); + $take = $count > 10 ? 2 : 1; + + $sprintIndex = 0; + $systemIndex = 0; + $i = 0; + while ($count > $i) { + Task::where('project_id', '=', $key) + ->where('system_id', 0)->where('sprint_id', 0) + ->take($take) + ->update( + [ + 'sprint_id' => isset($value['sprints'][$sprintIndex]) ? $value['sprints'][$sprintIndex] : 0, + 'system_id' => isset($value['systems'][$systemIndex]) ? $value['systems'][$systemIndex] : 0, + ]); + + $systemIndex += 1; + $sprintIndex += 1; + if ($systemIndex >= count($value['systems'])) { + $systemIndex = 0; + } + if ($sprintIndex >= count($value['sprints'])) { + $sprintIndex = 0; + } + + + $i += $take; + } + } + Task::whereIn('project_id', array_keys($projects)) + ->where('system_id', 0) + ->update(['system_id' => null]); + + Task::whereIn('project_id', array_keys($projects)) + ->where('sprint_id', 0) + ->update(['sprint_id' => null]); + } + + public function updateField($values, $count, $take, $field) + { + $i = 0; + $index = 0; + while ($count > $i) { + Task::where($field, '=', 0)->take($take)->update([$field => $values[$index]]); + $index += 1; + if (count($values) <= $index) { + $index = 0; + } + $i += $take; + } + } + + public function fillStatues($workflows) + { + foreach ($workflows as $key => $value) { + $count = Task::where('workflow_id', $key)->count(); + $take = $count > 10 ? 2 : 1; + + $index = 0; + $i = 0; + while ($count > $i) { + Task::where('workflow_id', '=', $key) + ->where('status_id', 0) + ->take($take) + ->update( + [ + 'status_id' => $value[$index] + ]); + + $index += 1; + if ($index >= count($value)) { + $index = 0; + } + + + $i += $take; + } + } + } + + public function fillTaskTag($count) + { + $tags = [5037, 5038, 5626]; + $tasks = Task::where('business_id', 1779)->get(); + foreach ($tasks as $task) { + $rand = rand(0,2); + $t = []; + do{ + array_push($t, new TagTask(['task_id' => $task->id, 'tag_id' => $tags[$rand]])); + $rand--; + }while($rand >= 0); + $task->tags()->saveMany($t); + } + + } +} diff --git a/database/seeds/TaskTmpSeeder.php b/database/seeds/TaskTmpSeeder.php new file mode 100644 index 0000000..38da127 --- /dev/null +++ b/database/seeds/TaskTmpSeeder.php @@ -0,0 +1,33 @@ +business_id)->first(); + $sprint = \App\Models\Sprint::where('business_id', $system->business_id)->first(); + $creator = \App\Models\Business::find($system->business_id)->owners()->first(); + + array_push($tasks, [ + 'title' => \Illuminate\Support\Str::random(5), + 'business_id' => $system->business_id, + 'project_id' => $system->project_id, + 'system_id' => $system->id, + 'workflow_id' => $status->workflow_id, + 'status_id' => $status->id, + 'sprint_id' => $sprint->id ?? null, + 'creator_id' => $creator->id, + ]); + if (sizeof($tasks) == 100) { + Task::insert($tasks); + $tasks = []; + } + } + } +} diff --git a/database/seeds/TransactionSeeder.php b/database/seeds/TransactionSeeder.php new file mode 100644 index 0000000..2d5fe36 --- /dev/null +++ b/database/seeds/TransactionSeeder.php @@ -0,0 +1,14 @@ +raw() + ); + } +} diff --git a/database/seeds/UserSeeder.php b/database/seeds/UserSeeder.php new file mode 100644 index 0000000..92bfaa1 --- /dev/null +++ b/database/seeds/UserSeeder.php @@ -0,0 +1,20 @@ +raw() + ); + Fingerprint::insert( + factory(Fingerprint::class, 5000)->raw() + ); + User::where('id', 1)->update(['mobile' => '09123456789']); + } +} diff --git a/database/seeds/WorkSeeder.php b/database/seeds/WorkSeeder.php new file mode 100755 index 0000000..b88ed2e --- /dev/null +++ b/database/seeds/WorkSeeder.php @@ -0,0 +1,36 @@ +raw() + ); + + $businessId = 1779; + Work::inRandomOrder()->take(150)->update(['business_id' => $businessId]); + + $projects = [3566, 3567, 3568]; + + foreach ($projects as $projectId) { + Work::where('business_id', $businessId) + ->whereNotIn('project_id', $projects) + ->take(50)->update(['project_id' => $projectId, 'started_at' => \Carbon\Carbon::now()]); + } + + $works = Work::where('business_id', $businessId)->get(); + foreach ($works as $work){ + \DB::table('works')->where('id', $work->id) + ->update(['started_at' => \Carbon\Carbon::now()->subDays(rand(0,90))]); + } + } +} diff --git a/database/seeds/WorkflowSeeder.php b/database/seeds/WorkflowSeeder.php new file mode 100644 index 0000000..f11678d --- /dev/null +++ b/database/seeds/WorkflowSeeder.php @@ -0,0 +1,50 @@ +raw([ + 'business_id' => $business_id, + ]); + $business_workflows[] = ++$workflow_inc; + for ($i=0; $i < rand(1, 4); $i++) { + $statuses[] = factory(Status::class)->raw([ + 'business_id' => $business_id, + 'workflow_id' => $workflow_inc, + ]); + $status_inc++; + } + } +// for ($i=0; $i < rand(1, 4); $i++) { +// $statuses[] = factory(Status::class)->raw([ +// 'business_id' => $business_id, +// ]); +// $status_inc++; +// for ($ws=0; $ws < rand(0, count($business_workflows)); $ws++) { +// $status_workflow[] = [ +// 'status_id' => $status_inc, +// 'workflow_id' => $business_workflows[rand(0, count($business_workflows)-1)], +// 'order' => rand(0, 10) +// ]; +// } +// } + } + DB::table('workflows')->insert($workflows); + DB::table('statuses')->insert($statuses); +// DB::table('status_workflow')->insert($status_workflow); + } +} diff --git a/definitions.json b/definitions.json new file mode 100644 index 0000000..10ab903 --- /dev/null +++ b/definitions.json @@ -0,0 +1,152 @@ +{ + "queues": [ + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "activity_queue", + "type": "classic", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "notif_queue", + "type": "classic", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "socket_queue", + "type": "classic", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "log_queue", + "type": "classic", + "vhost": "/" + } + ], + "exchanges": [ + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "activity_exchange", + "type": "headers", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "notif_exchange", + "type": "headers", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "socket_exchange", + "type": "headers", + "vhost": "/" + }, + { + "arguments": {}, + "auto_delete": false, + "durable": true, + "name": "log_exchange", + "type": "fanout", + "vhost": "/" + } + ], + "bindings": [ + { + "arguments": { + "x-match": "any", + "name" : "activity" + }, + "destination": "activity_queue", + "destination_type": "queue", + "routing_key": "", + "source": "activity_exchange", + "vhost": "/" + }, + { + "arguments": { + "x-match": "any", + "name" : "notif" + }, + "destination": "notif_queue", + "destination_type": "queue", + "routing_key": "", + "source": "notif_exchange", + "vhost": "/" + }, + { + "arguments": { + "x-match": "any", + "name" : "socket" + }, + "destination": "socket_queue", + "destination_type": "queue", + "routing_key": "", + "source": "socket_exchange", + "vhost": "/" + }, + { + "arguments": {}, + "destination": "log_queue", + "destination_type": "queue", + "routing_key": "", + "source": "log_exchange", + "vhost": "/" + } + ], + + "global_parameters": [ + { + "name": "cluster_name", + "value": "rabbit@258a143f3102" + } + ], + "parameters": [], + "permissions": [ + { + "configure": ".*", + "read": ".*", + "user": "root", + "vhost": "/", + "write": ".*" + } + ], + "policies": [], + "rabbit_version": "3.8.5", + "rabbitmq_version": "3.8.5", + "topic_permissions": [], + "users": [ + { + "hashing_algorithm": "rabbit_password_hashing_sha256", + "name": "root", + "password_hash": "Y0AhwjtK6iQM0t0Hp9t9nBey8zzMBa3LuszL3zlZ1mGtWsif", + "tags": "administrator" + } + ], + "vhosts": [ + { + "limits": [], + "metadata": { + "description": "Default virtual host", + "tags": [] + }, + "name": "/" + } + ] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d606ce7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,119 @@ +# For more information: https://laravel.com/docs/sail +version: '3' +services: + rabbitmq: + image: "rabbitmq:3-management" + ports: + - 5672:5672 + - 15672:15672 + environment: + RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG" + RABBITMQ_DEFAULT_USER: "root" + RABBITMQ_DEFAULT_PASS: "root" + RABBITMQ_DEFAULT_VHOST: "/" + networks: + - sail + volumes: + - ./definitions.json:/etc/rabbitmq/definitions.json + depends_on: + - laravel.test + minio: + image: minio/minio + command: server /data + ports: + - 9000:9000 + environment: + MINIO_ACCESS_KEY: root + MINIO_SECRET_KEY: minioroot + networks: + - sail + depends_on: + - laravel.test + commander: + image: rediscommander/redis-commander:latest + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + - HTTP_USER=root + - HTTP_PASSWORD=root + - REDIS_PASSWORD=root + ports: + - 8081:8081 + depends_on: + - redis + networks: + - sail + depends_on: + - redis + redis: + image: redis:latest + ports: + - 6379:6379 + command: redis-server --requirepass root + volumes: + - redis-data:/data + networks: + - sail + laravel.test: + build: + context: ./vendor/laravel/sail/runtimes/8.0 + dockerfile: Dockerfile + args: + WWWGROUP: '${WWWGROUP}' + image: sail-8.0/app + ports: + - '${APP_PORT:-80}:80' + environment: + WWWUSER: '${WWWUSER}' + LARAVEL_SAIL: 1 + volumes: + - '.:/var/www/html' + networks: + - sail + depends_on: + - mysql + mysql: + image: 'mysql:8.0' + ports: + - '${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' + MYSQL_DATABASE: '${DB_DATABASE}' + MYSQL_USER: '${DB_USERNAME}' + MYSQL_PASSWORD: '${DB_PASSWORD}' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + volumes: + - 'sailmysql:/var/lib/mysql' + networks: + - sail + healthcheck: + test: ["CMD", "mysqladmin", "ping"] + pma: + image: 'phpmyadmin:latest' + ports: + - 8080:80 + environment: + MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' + links: + - "mysql:db" + depends_on: + - 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 +volumes: + redis-data: + driver: local + sailmysql: + driver: local 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..9e5b113 --- /dev/null +++ b/resources/lang/fa/notification.php @@ -0,0 +1,86 @@ + [ + 'create' => 'یک تگ ساخته شد.' + ], + + 'business_user' => [ + 'create' =>[ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن کاربر به کسب و کار', + 'title' => 'افزودن کاربر به کسب و کار', + 'body' => 'کاربر :user به کسب و کار :business اضافه شد.' + ] + ], + + 'project_user' => [ + 'create' =>[ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن کاربر به پروژه', + 'title' => 'افزودن کاربر به پروژه', + 'body' => 'کاربر :user به پروژه :project اضافه شد.' + ] + ], + + 'tasks' => [ + 'create' => [ + 'assignee' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'تسک :task به شما واگذار شد.' + ], + 'approver' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'شما تایید کننده تسک :task شدید.' + ] + ], + 'update' => [ + 'assignee' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'تسک :task به شما واگذار شد.' + ], + 'approver' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'شما تایید کننده تسک :task شدید.' + ], + 'ready' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'تسک :task در حالت انتظار برای تست قرار دارد.' + ], + 'completed' => [ + 'greeting' => 'سلام کاربر گرامی!', + 'subject' => 'افزودن تسک جدید', + 'title' => 'افزودن تسک جدید', + 'body' => 'تسک :task به اتمام رسید.' + ] + ] + ], + + 'business' => [ + 'update' => 'کاربر :user به کسب و کار :business اضافه شد.', + 'update_wallet' => 'پول رو میدی یا پولت کنم', + ], + +]; 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' => 'لینک', + ], +]; diff --git a/routes/api.php b/routes/api.php index bcb8b18..be7bd04 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,19 +1,203 @@ get('/user', function (Request $request) { - return $request->user(); +/** @var \Illuminate\Routing\$router */ + +$router->group(['prefix' => 'actions'], function () use ($router) { + $router->group(['prefix' => 'businesses'], function () use ($router) { + $router->group(['prefix' => '{business}', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/info', 'BusinessController@info'); + }); + }); +}); + +$router->get('/callback', 'CreditController@callback'); +$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')->middleware('auth:api'); + $router->post('login', 'AuthController@login'); + $router->post('logout', 'AuthController@logout')->middleware('auth:api'); + $router->post('register', 'AuthController@register'); + $router->post('revoke/{token}', 'AuthController@revoke'); + + $router->post('forget-password', 'AuthController@forgetPassword'); + $router->post('update-password', 'AuthController@updatePassword'); + + $router->post('verification', 'AuthController@verification'); + + $router->get('google/redirect', 'AuthController@redirectToGoogle')->name('google.redirect'); + $router->get('google/callback', 'AuthController@handleGoogleCallback')->name('google.callback'); +}); + +$router->group(['prefix' => 'businesses', 'middleware' => 'auth:api'], function () use ($router) { + $router->get('/', 'BusinessController@index'); + $router->post('/', 'BusinessController@store'); + + $router->group(['prefix' => '{business}', 'middleware' => 'bindBusiness'], function () use ($router) { + + $router->get('/tasks', 'TaskController@index'); + $router->get('/works', 'WorkController@index'); + $router->get('statistics', 'StatisticController@index'); + + $router->put('/avatar', 'BusinessController@setAvatar'); + $router->delete('/avatar', 'BusinessController@unSetAvatar'); + + $router->get('/', 'BusinessController@show'); + $router->put('/', 'BusinessController@update'); + $router->delete('/', 'BusinessController@delete'); + $router->get('/restore', 'BusinessController@restore'); + + $router->get('/files', 'FileController@index'); + $router->get('/info', 'BusinessController@info'); + $router->get('/invoices', 'InvoiceController@index'); + $router->get('/invoices/{date}', 'InvoiceController@show'); + $router->get('/pay', 'CreditController@pay'); + $router->get('/payments', 'CreditController@payments'); + + + $router->group(['prefix' => 'projects'], function () use ($router) { + $router->get('/', 'ProjectController@index'); + $router->post('/', 'ProjectController@store'); + $router->group(['prefix' => '{project}'], function () use ($router) { + + $router->put('/avatar', 'ProjectController@setAvatar'); + $router->delete('/avatar', 'ProjectController@unSetAvatar'); + $router->get('/statistics', 'StatisticController@index'); + + $router->get('/', 'ProjectController@show'); + $router->put('/', 'ProjectController@update'); + $router->delete('/', 'ProjectController@delete'); + $router->group(['prefix' => 'files', 'as' => 'file.', 'middleware' => 'bindBusiness'], function () use ($router) { + // $router->get('/', ['as' => 'index', 'uses' => 'FileController@index']); + $router->post('/', ['as' => 'store', 'uses' => 'FileController@store']); + $router->group(['prefix' => '{file}'], function () use ($router) { + $router->get('/', ['as' => 'download', 'uses' => 'FileController@download']); + $router->put('/', ['as' => 'rename', 'uses' => 'FileController@rename']); + $router->delete('/', ['as' => 'delete', 'uses' => 'FileController@delete']); + }); + }); + $router->get('/restore', 'ProjectController@restore'); + + $router->group(['prefix' => 'sprints', 'as' => 'sprint.', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', ['as' => 'index', 'uses' => 'SprintController@index']); + $router->post('/', ['as' => 'store', 'uses' => 'SprintController@store']); + $router->group(['prefix' => '{sprint}'], function () use ($router) { + $router->get('/', ['as' => 'show', 'uses' => 'SprintController@show']); + $router->put('/', ['as' => 'update', 'uses' => 'SprintController@update']); + $router->delete('/', ['as' => 'delete', 'uses' => 'SprintController@delete']); + }); + }); + + $router->group(['prefix' => 'systems', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', 'SystemController@index'); + $router->post('/', 'SystemController@store'); + $router->group(['prefix' => '{system}'], function () use ($router) { + $router->get('/', 'SystemController@show'); + $router->put('/', 'SystemController@update'); + $router->delete('/', 'SystemController@delete'); + }); + }); + + $router->group(['prefix' => 'tasks/{task}/files', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', ['uses' => 'TaskFileController@index']); + $router->post('/', ['uses' => 'TaskFileController@sync']); + $router->get('/{file}', ['uses' => 'TaskFileController@download']); + }); + + $router->group(['prefix' => 'users'], function () use ($router){ + $router->post('/', 'ProjectController@storeOrUpdateUser'); + $router->group(['prefix' => '{user}'], function () use ($router) { + $router->delete('/', 'ProjectController@deleteUser'); + }); + }); + + $router->group(['prefix' => 'tasks'], function ($router) { + $router->post('/', 'TaskController@store'); + $router->group(['prefix' => '{task}'], function ($router) { + $router->get('/', 'TaskController@show'); + $router->put('/', 'TaskController@update'); + $router->delete('/', 'TaskController@destroy'); + + $router->post('/watchers', 'TaskController@toggleWatcher'); + + $router->group(['prefix' => 'works'], function ($router) { + $router->post('/', 'WorkController@store'); + $router->group(['prefix' => '{work}'], function ($router) { + $router->get('/', 'WorkController@show'); + $router->put('/', 'WorkController@update'); + $router->delete('/', 'WorkController@destroy'); + }); + }); + + $router->group(['prefix' => 'comments'], function ($router) { + $router->get('/', 'CommentController@index'); + $router->post('/', 'CommentController@store'); + $router->group(['prefix' => '{comment}'], function ($router) { + $router->get('/', 'CommentController@show'); + $router->put('/', 'CommentController@update'); + $router->delete('/', 'CommentController@destroy'); + }); + }); + + }); + }); + + }); + }); + + $router->group(['prefix' => 'tags', 'as' => 'tag.', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', ['as' => 'index', 'uses' => 'TagController@index']); + $router->post('/', ['as' => 'store', 'uses' => 'TagController@store']); + $router->group(['prefix' => '{tag}'], function () use ($router) { + $router->get('/', ['as' => 'show', 'uses' => 'TagController@show']); + $router->put('/', ['as' => 'update', 'uses' => 'TagController@update']); + $router->delete('/', ['as' => 'delete', 'uses' => 'TagController@delete']); + }); + }); + + $router->group(['prefix' => 'workflows', 'as' => 'workflow.', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', ['as' => 'index', 'uses' => 'WorkflowController@index']); + $router->post('/', ['as' => 'store', 'uses' => 'WorkflowController@store']); + $router->group(['prefix' => '{workflow}'], function () use ($router) { + $router->get('/', ['as' => 'show', 'uses' => 'WorkflowController@show']); + $router->put('/', ['as' => 'update', 'uses' => 'WorkflowController@update']); + $router->delete('/', ['as' => 'delete', 'uses' => 'WorkflowController@delete']); + $router->group(['prefix' => 'statuses', 'middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', 'StatusController@index'); + $router->post('/', 'StatusController@store'); + $router->group(['prefix' => '{status}'], function () use ($router) { + $router->get('/', 'StatusController@show'); + $router->put('/', 'StatusController@update'); + $router->delete('/', ['as' => 'delete', 'uses' => 'StatusController@delete']); + }); + }); + }); + }); + + $router->group(['prefix' => 'users'], function () use ($router){ + $router->post('/', 'BusinessController@storeOrUpdateUser'); + $router->group(['prefix' => '{user}'], function () use ($router) { + $router->delete('/', 'BusinessController@deleteUser'); + }); + }); + + $router->group(['prefix' => 'activities'], function () use ($router) { + $router->post('/', 'ActivityController@store'); + $router->get('/', 'ActivityController@index'); + }); + }); +}); + +$router->group(['prefix' => 'users','middleware' => 'bindBusiness'], function () use ($router) { + $router->get('/', 'UserController@index'); + $router->get('/search', 'UserController@search'); + $router->group(['prefix' => '{user}'], function () use ($router) { + $router->get('/', 'UserController@show'); + $router->put('/', 'UserController@update'); + + $router->put('/avatar', 'UserController@setAvatar'); + $router->delete('/avatar', 'UserController@unSetAvatar'); + }); });