dizox
4 years ago
187 changed files with 14384 additions and 1295 deletions
-
11.env.example
-
2.gitignore
-
5.idea/codeStyles/codeStyleConfig.xml
-
8.idea/liwo.iml
-
8.idea/modules.xml
-
93app/Channels/FcmChannel.php
-
132app/Channels/Messages/FcmMessage.php
-
169app/Console/Commands/CostCommand.php
-
1app/Enums/business.php
-
1app/Enums/comment.php
-
11app/Enums/cruds.php
-
1app/Enums/levels.php
-
25app/Enums/log.php
-
1app/Enums/post.php
-
1app/Enums/roles.php
-
1app/Enums/service.php
-
1app/Enums/status.php
-
83app/Enums/tables.php
-
1app/Enums/ticket.php
-
1app/Enums/user.php
-
20app/Events/BusinessUpdate.php
-
38app/Events/BusinessUserCreate.php
-
38app/Events/ModelSaved.php
-
24app/Events/ProjectUserCreate.php
-
38app/Events/TagCreate.php
-
28app/Events/TaskCreate.php
-
28app/Events/TaskUpdate.php
-
110app/Http/Controllers/ActivityController.php
-
268app/Http/Controllers/AuthController.php
-
195app/Http/Controllers/BusinessController.php
-
90app/Http/Controllers/CommentController.php
-
4app/Http/Controllers/Controller.php
-
69app/Http/Controllers/CreditController.php
-
207app/Http/Controllers/FileController.php
-
80app/Http/Controllers/InvoiceController.php
-
159app/Http/Controllers/ProjectController.php
-
45app/Http/Controllers/SprintController.php
-
112app/Http/Controllers/StatisticController.php
-
35app/Http/Controllers/StatusController.php
-
40app/Http/Controllers/SystemController.php
-
34app/Http/Controllers/TagController.php
-
295app/Http/Controllers/TaskController.php
-
87app/Http/Controllers/TaskFileController.php
-
70app/Http/Controllers/UserController.php
-
242app/Http/Controllers/WorkController.php
-
67app/Http/Controllers/WorkflowController.php
-
2app/Http/Kernel.php
-
34app/Http/Resources/BusinessResource.php
-
34app/Http/Resources/CommentResource.php
-
35app/Http/Resources/FileResource.php
-
34app/Http/Resources/FingerprintResource.php
-
38app/Http/Resources/ProjectResource.php
-
23app/Http/Resources/TaskCollection.php
-
30app/Http/Resources/TaskResource.php
-
32app/Http/Resources/TransactionResource.php
-
34app/Http/Resources/UserResource.php
-
73app/Listeners/ActivityRegistration.php
-
32app/Listeners/BusinessUpdateListener.php
-
93app/Listeners/BusinessUserCreateNotif.php
-
36app/Listeners/NotifHandler.php
-
93app/Listeners/ProjectUserCreateNotif.php
-
37app/Listeners/TagCreateNotif.php
-
104app/Listeners/TaskCreateNotif.php
-
155app/Listeners/TaskUpdateNotif.php
-
38app/Models/Activity.php
-
487app/Models/Business.php
-
19app/Models/Comment.php
-
28app/Models/Cost.php
-
75app/Models/File.php
-
31app/Models/Fingerprint.php
-
237app/Models/Model.php
-
206app/Models/Project.php
-
140app/Models/ReportableRelation.php
-
29app/Models/SoftDeletes.php
-
70app/Models/Sprint.php
-
53app/Models/Status.php
-
66app/Models/System.php
-
56app/Models/Tag.php
-
12app/Models/TagTask.php
-
307app/Models/Task.php
-
164app/Models/Transaction.php
-
206app/Models/User.php
-
126app/Models/Work.php
-
89app/Models/Workflow.php
-
49app/Notifications/DBNotification.php
-
51app/Notifications/FcmNotification.php
-
78app/Notifications/MailNotification.php
-
10app/Providers/AppServiceProvider.php
-
22app/Providers/AuthServiceProvider.php
-
38app/Providers/EventServiceProvider.php
-
2app/Providers/RouteServiceProvider.php
-
40app/Rules/MaxBound.php
-
15app/Utilities/Avatar/DefaultConversionFileNamer.php
-
41app/Utilities/Avatar/DefaultPathGenerator.php
-
43app/Utilities/BusinessInfoRequestMixin.php
-
58app/Utilities/Exceptions/Handler.php
-
72app/Utilities/HelperClass/NotificationHelper.php
-
27app/Utilities/Helpers/enum.php
-
1app/Utilities/Helpers/http.php
-
1app/Utilities/Helpers/index.php
@ -1,5 +0,0 @@ |
|||
<component name="ProjectCodeStyleConfiguration"> |
|||
<state> |
|||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> |
|||
</state> |
|||
</component> |
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<module type="WEB_MODULE" version="4"> |
|||
<component name="NewModuleRootManager"> |
|||
<content url="file://$MODULE_DIR$" /> |
|||
<orderEntry type="inheritedJdk" /> |
|||
<orderEntry type="sourceFolder" forTests="false" /> |
|||
</component> |
|||
</module> |
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ProjectModuleManager"> |
|||
<modules> |
|||
<module fileurl="file://$PROJECT_DIR$/.idea/liwo.iml" filepath="$PROJECT_DIR$/.idea/liwo.iml" /> |
|||
</modules> |
|||
</component> |
|||
</project> |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Channels; |
|||
|
|||
use App\Channels\Messages\FcmMessage; |
|||
use Illuminate\Notifications\Notification; |
|||
use GuzzleHttp\Client as HttpClient; |
|||
|
|||
class FcmChannel |
|||
{ |
|||
/** |
|||
* The API URL for FCM. |
|||
* |
|||
* @var string |
|||
*/ |
|||
const API_URI = 'https://fcm.googleapis.com/fcm/send'; |
|||
|
|||
/** |
|||
* The HTTP client instance. |
|||
* |
|||
* @var \GuzzleHttp\Client |
|||
*/ |
|||
protected $http; |
|||
|
|||
/** |
|||
* The FCM API key. |
|||
* |
|||
* @var string |
|||
*/ |
|||
protected $apikey; |
|||
|
|||
/** |
|||
* Create a new FCM channel instance. |
|||
* |
|||
* @param \GuzzleHttp\Client $http |
|||
* @param string $apiKey |
|||
* @return void |
|||
*/ |
|||
public function __construct(HttpClient $http, string $apiKey) |
|||
{ |
|||
$this->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; |
|||
} |
|||
} |
@ -0,0 +1,132 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Channels\Messages; |
|||
|
|||
|
|||
class FcmMessage |
|||
{ |
|||
/** |
|||
* The devices token to send the message from. |
|||
* |
|||
* @var array|string |
|||
*/ |
|||
public $to; |
|||
|
|||
/** |
|||
* The topic of the FCM message. |
|||
* |
|||
* @var array |
|||
*/ |
|||
public $topic; |
|||
|
|||
/** |
|||
* The data of the FCM message. |
|||
* |
|||
* @var array |
|||
*/ |
|||
public $data; |
|||
|
|||
/** |
|||
* The notification body of the FCM message. |
|||
* |
|||
* @var array |
|||
*/ |
|||
public $notification; |
|||
|
|||
/** |
|||
* The condition for receive the FCM message. |
|||
* |
|||
* @var array |
|||
*/ |
|||
public $condition; |
|||
|
|||
/** |
|||
* The priority of the FCM message. |
|||
* |
|||
* @var string |
|||
*/ |
|||
public $priority = 'normal'; |
|||
|
|||
/** |
|||
* Set the devices token to send the message from. |
|||
* |
|||
* @param array|string $to |
|||
* @return $this |
|||
*/ |
|||
public function to($to) |
|||
{ |
|||
if (is_array($to) && count($to) === 1) { |
|||
$this->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; |
|||
} |
|||
} |
@ -0,0 +1,169 @@ |
|||
<?php |
|||
|
|||
namespace App\Console\Commands; |
|||
|
|||
use Throwable; |
|||
use Carbon\Carbon; |
|||
use App\Models\Cost; |
|||
use App\Models\Business; |
|||
use Illuminate\Console\Command; |
|||
use Morilog\Jalali\CalendarUtils; |
|||
use Illuminate\Support\Facades\DB; |
|||
use Illuminate\Support\Facades\Cache; |
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class CostCommand extends Command |
|||
{ |
|||
protected $signature = 'cost:work'; |
|||
|
|||
protected $description = 'Run the cost worker'; |
|||
|
|||
public function handle() |
|||
{ |
|||
while (true) { |
|||
$lock = Cache::get('lock', false); |
|||
if ($lock) { |
|||
$this->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
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
<?php
return [
'fee' => [
'user' => 100,
'file' => 200,
]
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'status' => [
'reject' => [
'id' => 10,
'label' => 'رد'
],
'approve' => [
'id' => 20,
'label' => 'تایید'
],
]
]; |
@ -0,0 +1,11 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
|
|||
'inverse' => [ |
|||
10 => ['name' => 'Create', 'singular_name' => 'create'], |
|||
20 => ['name' => 'Update', 'singular_name' => 'update'], |
|||
30 => ['name' => 'Delete', 'singular_name' => 'delete'], |
|||
], |
|||
|
|||
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'owner' => [
'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' => 'غیر فعال'
],
]; |
@ -0,0 +1,25 @@ |
|||
<?php |
|||
|
|||
use App\Models\User; |
|||
use App\Models\Business; |
|||
use App\Project; |
|||
use App\Task; |
|||
use App\SpentHour; |
|||
|
|||
return [ |
|||
|
|||
'types' => [ |
|||
User::class => 10, |
|||
Business::class => 20, |
|||
Project::class => 30, |
|||
Task::class => 40, |
|||
Spenthour::class => 50, |
|||
], |
|||
|
|||
'actions' => [ |
|||
'created' => 10, |
|||
'updated' => 20, |
|||
'deleted' => 30, |
|||
'restored' => 40, |
|||
], |
|||
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'status' => [
'draft' => [
'id' => 10,
'label' => 'پیش نویس'
],
'review' => [
'id' => 20,
'label' => 'در حال بررسی'
],
'published' => [
'id' => 30,
'label' => 'منتشر'
],
'trashed' => [
'id' => 40,
'label' => 'حذف'
],
]
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'hidden' => [
'id' => 0,
'label' => 'غیر فعال'
],
'guest' => [
'id' => 1,
'label' => 'میهمان'
],
'Colleague' => [
'id' => 2,
'label' => 'همکار'
],
'senior' => [
'id' => 3,
'label' => 'معاون'
],
'manager' => [
'id' => 4,
'label' => 'مدیر'
],
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'post' => [
'file' => [
'orphanage' => [
'id' => 'orphanage',
'label' => 'دادههای موقت',
]
]
]
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'states' => [
'inactive' => [
'id' => 0,
'label' => 'غیر فعال',
'name' => 'Inactive'
],
'active' => [
'id' => 1,
'label' => 'فعال',
'name' => 'Active'
],
'close' => [
'id' => 2,
'label' => 'بسته',
'name' => 'Close'
],
'done' => [
'id' => 3,
'label' => 'انجام شده',
'name' => 'Done'
],
],
]; |
@ -0,0 +1,83 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
|
|||
'inverse' => [ |
|||
10 => ['name' => 'Businesses'], |
|||
20 => ['name' => 'Projects'], |
|||
30 => ['name' => 'Sprints'], |
|||
40 => ['name' => 'Statuses'], |
|||
50 => ['name' => 'Systems'], |
|||
60 => ['name' => 'Workflows'], |
|||
70 => ['name' => 'Tags'], |
|||
80 => ['name' => 'Tasks'], |
|||
90 => ['name' => 'Works'], |
|||
210 => ['name' => 'BusinessUser'], |
|||
220 => ['name' => 'ProjectUser'], |
|||
230 => ['name' => 'TagTask'], |
|||
], |
|||
|
|||
//Main Table's
|
|||
'businesses' => [ |
|||
'id' => 10, |
|||
'name' => 'Businesses', |
|||
'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', |
|||
], |
|||
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'type' => [
'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' => 'بسته'
]
]
]; |
@ -0,0 +1 @@ |
|||
<?php
return [
'type' => [
'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',
]
]; |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Queue\SerializesModels; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
|
|||
class BusinessUpdate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class BusinessUserCreate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
|
|||
/** |
|||
* Get the channels the event should broadcast on. |
|||
* |
|||
* @return \Illuminate\Broadcasting\Channel|array |
|||
*/ |
|||
public function broadcastOn() |
|||
{ |
|||
return new PrivateChannel('channel-name'); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class ModelSaved |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
|
|||
/** |
|||
* Get the channels the event should broadcast on. |
|||
* |
|||
* @return \Illuminate\Broadcasting\Channel|array |
|||
*/ |
|||
public function broadcastOn() |
|||
{ |
|||
return new PrivateChannel('channel-name'); |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class ProjectUserCreate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class TagCreate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
|
|||
/** |
|||
* Get the channels the event should broadcast on. |
|||
* |
|||
* @return \Illuminate\Broadcasting\Channel|array |
|||
*/ |
|||
public function broadcastOn() |
|||
{ |
|||
return new PrivateChannel('channel-name'); |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class TaskCreate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class TaskUpdate |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->message = $message; |
|||
} |
|||
} |
@ -0,0 +1,110 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Activity; |
|||
use App\Rules\MaxBound; |
|||
use Illuminate\Http\Request; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
|
|||
class ActivityController extends Controller |
|||
{ |
|||
public function index($business, Request $request) |
|||
{ |
|||
permit('businessAccess'); |
|||
$this->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() |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,268 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\User; |
|||
use App\Models\Business; |
|||
use App\Models\Fingerprint; |
|||
use Illuminate\Support\Str; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Validation\Rule; |
|||
use Illuminate\Http\JsonResponse; |
|||
use App\Http\Resources\UserResource; |
|||
use Illuminate\Support\Facades\Auth; |
|||
use Illuminate\Support\Facades\Hash; |
|||
use Illuminate\Support\Facades\Cache; |
|||
use Laravel\Socialite\Facades\Socialite; |
|||
use Illuminate\Session\TokenMismatchException; |
|||
use Symfony\Component\HttpFoundation\Response; |
|||
|
|||
class AuthController extends Controller |
|||
{ |
|||
public function redirectToGoogle() |
|||
{ |
|||
return Socialite::driver('google')->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); |
|||
} |
|||
} |
@ -0,0 +1,195 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\User; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Support\Facades\DB; |
|||
use Illuminate\Support\Facades\Auth; |
|||
|
|||
class BusinessController extends Controller |
|||
{ |
|||
public function index() |
|||
{ |
|||
return auth()->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); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,90 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Task; |
|||
use App\Models\Comment; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\Response; |
|||
|
|||
class CommentController extends Controller |
|||
{ |
|||
public function index($business, $project, $task) |
|||
{ |
|||
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::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
|
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Transaction; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Support\Facades\Auth; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
use App\Http\Resources\TransactionResource; |
|||
|
|||
class CreditController extends Controller |
|||
{ |
|||
public function payments(Request $request, int $business) |
|||
{ |
|||
\permit('isBusinessOwner'); |
|||
|
|||
$query = Transaction::where('business_id', $business); |
|||
|
|||
$builder = QueryBuilder::for($query) |
|||
->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("تراکنش تایید نشد"); |
|||
} |
|||
} |
@ -0,0 +1,207 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\File; |
|||
use App\Models\Project; |
|||
use App\Rules\MaxBound; |
|||
use App\Models\Business; |
|||
use Illuminate\Support\Str; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\UploadedFile; |
|||
use App\Http\Resources\FileResource; |
|||
use Illuminate\Support\Facades\Auth; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class FileController extends Controller |
|||
{ |
|||
public function index(int $business, Request $request) |
|||
{ |
|||
permit('businessAccess'); |
|||
|
|||
$this->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(); |
|||
} |
|||
} |
@ -0,0 +1,80 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Cost; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
|
|||
|
|||
class InvoiceController extends Controller |
|||
{ |
|||
|
|||
public function index(Request $request, int $business) |
|||
{ |
|||
$business = Business::findOrFail($business); |
|||
|
|||
$builder = Cost::select('month') |
|||
->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(); |
|||
} |
|||
} |
@ -0,0 +1,159 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Project; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Support\Facades\DB; |
|||
|
|||
class ProjectController extends Controller |
|||
{ |
|||
public function index(Request $request, int $business) |
|||
{ |
|||
// permit('businessAccess');
|
|||
return Project::where('business_id', $business)->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; |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Sprint; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class SprintController extends Controller |
|||
{ |
|||
|
|||
public function store($business, $project, Request $request) |
|||
{ |
|||
permit('projectSprints', ['project_id' => $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); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,112 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use Illuminate\Support\Arr; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Support\Facades\DB; |
|||
|
|||
class StatisticController extends Controller |
|||
{ |
|||
public $map = [ |
|||
'test' => 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); |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Status; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\Response; |
|||
use Illuminate\Validation\Rule; |
|||
|
|||
class StatusController extends Controller |
|||
{ |
|||
public function store($business, $workflow, Request $request) |
|||
{ |
|||
permit('businessStatuses'); |
|||
Status::create($request->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); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\System; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class SystemController extends Controller |
|||
{ |
|||
public function store($business, $project, Request $request) |
|||
{ |
|||
permit('projectSystems', ['project_id' => $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); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Tag; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class TagController extends Controller |
|||
{ |
|||
public function store($business, Request $request) |
|||
{ |
|||
permit('businessTags'); |
|||
Tag::create($request->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); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,295 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use Carbon\Carbon; |
|||
use App\Models\Task; |
|||
use App\Models\Work; |
|||
use App\Models\TagTask; |
|||
use App\Rules\MaxBound; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\Response; |
|||
use Illuminate\Support\Facades\DB; |
|||
use App\Http\Resources\TaskResource; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use App\Http\Resources\TaskCollection; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
|
|||
class TaskController extends Controller |
|||
{ |
|||
public function index($business, Request $request) |
|||
{ |
|||
permit('businessAccess'); |
|||
$per_page = $request->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); |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use Auth; |
|||
use App\Models\File; |
|||
use App\Models\Task; |
|||
use App\Models\Project; |
|||
use App\Models\Business; |
|||
use Illuminate\Http\Request; |
|||
use App\Http\Controllers\Controller; |
|||
use App\Http\Resources\FileResource; |
|||
use Symfony\Component\HttpFoundation\Response; |
|||
|
|||
class TaskFileController extends Controller |
|||
{ |
|||
public function checkBelonging(int $business, int $project, int $task) |
|||
{ |
|||
$business = Business::findOrFail($business); |
|||
$project = Project::findOrFail($project); |
|||
$task = Task::find($task); |
|||
|
|||
if ( |
|||
$business->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(); |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
|
|||
use App\Models\User; |
|||
use Illuminate\Http\Request; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
|
|||
class UserController extends Controller |
|||
{ |
|||
public function search(Request $request) |
|||
{ |
|||
$limit = 20; |
|||
$userQ = User::query(); |
|||
if ($request->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; |
|||
} |
|||
} |
@ -0,0 +1,242 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use Carbon\Carbon; |
|||
use App\Models\Task; |
|||
use App\Models\Work; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\Response; |
|||
use Spatie\QueryBuilder\QueryBuilder; |
|||
use Spatie\QueryBuilder\AllowedFilter; |
|||
|
|||
class WorkController extends Controller |
|||
{ |
|||
public function index($business, Request $request) |
|||
{ |
|||
permit('businessAccess'); |
|||
$per_page = $request->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']); |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Business; |
|||
use App\Models\Workflow; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class WorkflowController extends Controller |
|||
{ |
|||
public function store($business, Request $request) |
|||
{ |
|||
permit('businessWorkFlows'); |
|||
Workflow::create($request->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); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class BusinessResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class CommentResource extends JsonResource |
|||
{ |
|||
/** |
|||
* Transform the resource into an array. |
|||
* |
|||
* @param \Illuminate\Http\Request $request |
|||
* @return array |
|||
*/ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class FileResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class FingerprintResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class ProjectResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Carbon\Carbon; |
|||
use Illuminate\Http\Resources\Json\ResourceCollection; |
|||
|
|||
class TaskCollection extends ResourceCollection |
|||
{ |
|||
/** |
|||
* Transform the resource collection into an array. |
|||
* |
|||
* @param \Illuminate\Http\Request $request |
|||
* @return array |
|||
*/ |
|||
public function toArray($request) |
|||
{ |
|||
return [ |
|||
'tasks' => TaskResource::collection($this->collection), |
|||
'now' => Carbon::now()->toDateString() |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class TaskResource extends JsonResource |
|||
{ |
|||
/** |
|||
* Transform the resource into an array. |
|||
* |
|||
* @param \Illuminate\Http\Request $request |
|||
* @return array |
|||
*/ |
|||
public function toArray($request) |
|||
{ |
|||
foreach ($this->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; |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
|
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class TransactionResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Resources; |
|||
use Illuminate\Http\Resources\Json\JsonResource; |
|||
|
|||
class UserResource extends JsonResource |
|||
{ |
|||
public function toArray($request) |
|||
{ |
|||
$resource = [ |
|||
'_service' => '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; |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\ModelSaved; |
|||
use App\Models\Activity; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
use Illuminate\Support\Facades\Log; |
|||
|
|||
class ActivityRegistration implements ShouldQueue |
|||
{ |
|||
/** |
|||
* If your queue connection's after_commit configuration option is set to true, |
|||
* indicate that a particular queued listener should be dispatched after all database transactions closed |
|||
* |
|||
* @var boolean |
|||
*/ |
|||
public $afterCommit = true; |
|||
|
|||
/** |
|||
* The name of the connection the job should be sent to. |
|||
* |
|||
* @var string|null |
|||
*/ |
|||
public $connection = 'redis'; |
|||
|
|||
/** |
|||
* The name of the queue the job should be sent to. |
|||
* It's just a name and if don't set it, laravel use default queue name in the connection |
|||
* |
|||
* @var string|null |
|||
*/ |
|||
public $queue = 'activities'; |
|||
|
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param ModelSaved $event |
|||
* @return void |
|||
*/ |
|||
public function handle(ModelSaved $event) |
|||
{ |
|||
Log::info('listener:'. json_encode($event->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, |
|||
]); |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Models\User; |
|||
use App\Models\Business; |
|||
use Illuminate\Support\Arr; |
|||
use App\Events\BusinessUpdate; |
|||
use App\Events\BusinessUserCreate; |
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
|
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class BusinessUpdateListener |
|||
{ |
|||
public function handle(BusinessUpdate $event) |
|||
{ |
|||
$payload = $event->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)); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Channels\FcmChannel; |
|||
use App\Events\BusinessUserCreate; |
|||
use App\Models\Business; |
|||
use App\Models\User; |
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\FcmNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class BusinessUserCreateNotif |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param BusinessUserCreate $event |
|||
* @return void |
|||
*/ |
|||
public function handle(BusinessUserCreate $event) |
|||
{ |
|||
$payload = $event->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)); |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\ModelSaved; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
|
|||
class NotifHandler |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param ModelSaved $event |
|||
* @return void |
|||
*/ |
|||
public function handle(ModelSaved $event) |
|||
{ |
|||
$message = json_decode($event->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); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\ProjectUserCreate; |
|||
use App\Models\Project; |
|||
use App\Models\User; |
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\FcmNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class ProjectUserCreateNotif |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param ProjectUserCreate $event |
|||
* @return void |
|||
*/ |
|||
public function handle(ProjectUserCreate $event) |
|||
{ |
|||
$payload = $event->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)); |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\TagCreate; |
|||
use App\Models\User; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Queue\InteractsWithQueue; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class TagCreateNotif |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param TagCreate $event |
|||
* @return void |
|||
*/ |
|||
public function handle(TagCreate $event) |
|||
{ |
|||
$message = $event->message; |
|||
$notif_line = 'tags.create'; |
|||
$users = User::where('id', '<', 10)->get(); |
|||
Notification::send($users, new MailNotification($notif_line)); |
|||
} |
|||
} |
@ -0,0 +1,104 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\TaskCreate; |
|||
use App\Models\Task; |
|||
use App\Models\User; |
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\FcmNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class TaskCreateNotif |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param TaskCreate $event |
|||
* @return void |
|||
*/ |
|||
public function handle(TaskCreate $event) |
|||
{ |
|||
$payload = $event->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)); |
|||
} |
|||
} |
@ -0,0 +1,155 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\Events\TaskUpdate; |
|||
use App\Models\Task; |
|||
use App\Models\User; |
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\FcmNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class TaskUpdateNotif |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param TaskUpdate $event |
|||
* @return void |
|||
*/ |
|||
public function handle(TaskUpdate $event) |
|||
{ |
|||
$payload = $event->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)); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Carbon\Carbon; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Activity extends Model |
|||
{ |
|||
use HasFactory; |
|||
|
|||
protected $fillable = [ |
|||
'business_id', 'project_id', 'system_id', 'workflow_id', 'status_id', 'sprint_id', |
|||
'actor_id', 'task_id', 'subject_id', 'user_id', 'crud_id', 'table_id', 'original', 'diff' |
|||
]; |
|||
|
|||
public $casts = [ |
|||
'original' => '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; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,487 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\File; |
|||
use App\Models\Model; |
|||
use App\Models\SoftDeletes; |
|||
use App\Models\ReportableRelation; |
|||
use Illuminate\Validation\Rule; |
|||
use Illuminate\Http\UploadedFile; |
|||
use Spatie\MediaLibrary\HasMedia; |
|||
use Illuminate\Support\Facades\Cache; |
|||
use Spatie\MediaLibrary\InteractsWithMedia; |
|||
use Spatie\MediaLibrary\MediaCollections\Models\Media; |
|||
|
|||
class Business extends Model implements HasMedia |
|||
{ |
|||
use SoftDeletes, |
|||
InteractsWithMedia; |
|||
|
|||
|
|||
const CONVERSION_NAME = 'avatar'; |
|||
|
|||
const COLLECTION_NAME = 'avatars'; |
|||
|
|||
public static $permissions = ['level']; |
|||
|
|||
protected $table = 'businesses'; |
|||
|
|||
protected $fillable = ['name', 'slug', 'wallet','files_volume','cache', 'color', 'description', 'has_avatar', 'calculated_at', 'users']; |
|||
|
|||
protected $fillable_relations = [ |
|||
'users' |
|||
]; |
|||
|
|||
protected $reportable = [ |
|||
'name', 'slug', 'wallet', 'files_volume', 'cache', 'calculated_at', // fields
|
|||
['users' => '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; |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
|
|||
class Comment extends Model |
|||
{ |
|||
use HasFactory; |
|||
protected $fillable = ['business_id', 'project_id', 'task_id', 'user_id', 'body']; |
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'body' => 'required|string|min:3|max:1000', |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Cost extends Model |
|||
{ |
|||
public const USER_TYPE = 'users'; |
|||
|
|||
public const FILE_TYPE = 'files'; |
|||
|
|||
public $perPage = 12; |
|||
|
|||
protected $fillable = [ |
|||
'business_id', 'type', 'month', 'amount', 'fee', 'duration','cost','tax', 'additional' |
|||
]; |
|||
|
|||
public $casts = [ |
|||
'additional' => 'array', |
|||
'tax' => 'float', |
|||
]; |
|||
|
|||
public function business() |
|||
{ |
|||
return $this->belongsTo(Business::class, 'business_id'); |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class File extends Model |
|||
{ |
|||
protected $table = 'files'; |
|||
|
|||
protected $fillable = [ |
|||
'user_id', 'business_id', 'project_id', 'disk', 'original_name', 'name', |
|||
'extension', 'mime', 'group','size', 'description' |
|||
]; |
|||
|
|||
protected $casts = []; |
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'user_id' => '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, |
|||
] |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Fingerprint extends Model |
|||
{ |
|||
protected $fillable = ['user_id', 'agent', 'ip', 'os', 'latitude', 'longitude', 'token', 'fcm_token']; |
|||
|
|||
protected $table = 'fingerprints'; |
|||
|
|||
public function user() |
|||
{ |
|||
return $this->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', |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,237 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Events\ModelSaved; |
|||
use Illuminate\Http\JsonResponse; |
|||
use Illuminate\Support\Facades\DB; |
|||
use Illuminate\Support\Facades\Auth; |
|||
use Illuminate\Validation\Validator; |
|||
use Illuminate\Database\Eloquent\Collection; |
|||
use Illuminate\Validation\ValidationException; |
|||
use Symfony\Component\HttpFoundation\Response; |
|||
use Illuminate\Database\Eloquent\Model as EloquentModel; |
|||
|
|||
|
|||
class Model extends EloquentModel |
|||
{ |
|||
|
|||
/** |
|||
* Introducing model relationships |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $fillable_relations = []; |
|||
|
|||
/** |
|||
* Models that are ready to change. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $filled_relations = []; |
|||
|
|||
/** |
|||
* Models that are ready to change. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $reportable = []; |
|||
|
|||
protected $dirties = []; |
|||
|
|||
protected $action = null; |
|||
|
|||
public const CREATED = 10; |
|||
|
|||
public const UPDATED = 20; |
|||
|
|||
public const DELETED = 30; |
|||
|
|||
public const RESTORED = 40; |
|||
|
|||
protected static function booted() |
|||
{ |
|||
static::created(function ($model) { |
|||
$model->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; |
|||
} |
|||
} |
@ -0,0 +1,206 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\File; |
|||
use App\Models\Model; |
|||
use App\Models\SoftDeletes; |
|||
use App\Models\ReportableRelation; |
|||
use Illuminate\Validation\Rule; |
|||
use Illuminate\Http\UploadedFile; |
|||
use Spatie\MediaLibrary\HasMedia; |
|||
use Spatie\MediaLibrary\InteractsWithMedia; |
|||
use Spatie\MediaLibrary\MediaCollections\Models\Media; |
|||
|
|||
class Project extends Model implements HasMedia |
|||
{ |
|||
use SoftDeletes, |
|||
InteractsWithMedia; |
|||
|
|||
|
|||
const CONVERSION_NAME = 'avatar'; |
|||
|
|||
const COLLECTION_NAME = 'avatars'; |
|||
|
|||
public static $permissions = ['level']; |
|||
|
|||
|
|||
protected $table = 'projects'; |
|||
|
|||
protected $fillable = [ |
|||
'business_id', 'name', 'slug', 'private', 'budget', 'color', 'active', 'description', 'has_avatar', 'start', 'finish','members' |
|||
]; |
|||
|
|||
protected $reportable = [ |
|||
'business_id', 'name', 'slug', ['members' => '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; |
|||
} |
|||
} |
@ -0,0 +1,140 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Anik\Amqp\Exchange; |
|||
use Anik\Amqp\Facades\Amqp; |
|||
use App\Events\ModelSaved; |
|||
use PhpAmqpLib\Wire\AMQPTable; |
|||
use Anik\Amqp\PublishableMessage; |
|||
use Illuminate\Support\Facades\Auth; |
|||
use Illuminate\Database\Eloquent\Relations\Pivot; |
|||
|
|||
class ReportableRelation extends Pivot |
|||
{ |
|||
public const CREATED = 10; |
|||
|
|||
public const UPDATED = 20; |
|||
|
|||
public const DELETED = 30; |
|||
|
|||
protected static function booted() |
|||
{ |
|||
static::created(function ($model) { |
|||
$model->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' => '', |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Illuminate\Database\Eloquent\SoftDeletes as SoftDeletesOriginal; |
|||
|
|||
trait SoftDeletes |
|||
{ |
|||
use SoftDeletesOriginal; |
|||
|
|||
protected function performDeleteOnModel() |
|||
{ |
|||
if ($this->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(); |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Sprint extends Model |
|||
{ |
|||
protected $table = 'sprints'; |
|||
|
|||
protected $fillable = ['business_id', 'project_id', 'name', 'description', 'started_at', 'ended_at', 'active']; |
|||
|
|||
protected $reportable = [ |
|||
'name', 'started_at', 'ended_at', // fields
|
|||
]; |
|||
|
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' => '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'); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,53 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Status extends Model |
|||
{ |
|||
protected $table = 'statuses'; |
|||
|
|||
protected $fillable = [ |
|||
'business_id', 'workflow_id', 'name', 'state', 'order' |
|||
]; |
|||
|
|||
protected $reportable = [ |
|||
'name', 'state', 'order' |
|||
]; |
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' =>'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'); |
|||
} |
|||
} |
@ -0,0 +1,66 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class System extends Model |
|||
{ |
|||
protected $table = 'systems'; |
|||
|
|||
protected $fillable = ['business_id', 'project_id', 'name']; |
|||
|
|||
protected $reportable = [ |
|||
'name' |
|||
]; |
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' => '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'); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,56 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Tag extends Model |
|||
{ |
|||
protected $fillable = ['business_id', 'label', 'color']; |
|||
|
|||
protected $reportable = [ |
|||
'label', 'color' |
|||
]; |
|||
|
|||
public function getValueOf(?string $key) |
|||
{ |
|||
$values = [ |
|||
'business_id' => $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__ |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class TagTask extends Model |
|||
{ |
|||
protected $table = 'tag_task'; |
|||
public $incrementing = true; |
|||
protected $fillable = ['tag_id', 'task_id']; |
|||
} |
@ -0,0 +1,307 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\ReportableRelation; |
|||
use Carbon\Carbon; |
|||
use App\Models\Tag; |
|||
use App\Models\User; |
|||
use App\Models\Work; |
|||
use App\Models\Model; |
|||
use App\Models\Comment; |
|||
use App\Models\Project; |
|||
use App\Models\TagTask; |
|||
use App\Models\Business; |
|||
use Illuminate\Validation\Rule; |
|||
use Illuminate\Support\Facades\DB; |
|||
use Illuminate\Validation\Rules\RequiredIf; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
|
|||
class Task extends Model |
|||
{ |
|||
use HasFactory; |
|||
protected $fillable = [ |
|||
'business_id', 'project_id', 'creator_id', 'workflow_id', 'assignee_id', 'system_id', 'sprint_id', |
|||
'status_id', 'approver_id', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at', |
|||
'work_start', 'spent_time', 'work_finish', 'ready_to_test', 'watchers', 'tags' |
|||
]; |
|||
|
|||
protected $reportable = [ |
|||
'priority', 'title', 'description', 'priority', 'on_time', 'estimated_time', 'due_date', 'completed_at', |
|||
'work_finish', 'ready_to_test', 'assignee_id', 'approver_id', ['tags' => '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'); |
|||
} |
|||
} |
@ -0,0 +1,164 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
use Illuminate\Support\Arr; |
|||
use App\Utilities\Zarinpal\Laravel\Facade\Zarinpal; |
|||
|
|||
class Transaction extends Model |
|||
{ |
|||
protected $table = 'transactions'; |
|||
|
|||
protected $fillable = [ |
|||
'user_id', 'business_id', 'amount', 'succeeded', 'options' |
|||
]; |
|||
|
|||
|
|||
protected $casts = [ |
|||
'options' => '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(); |
|||
} |
|||
} |
@ -0,0 +1,126 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Carbon\Carbon; |
|||
use App\Models\Task; |
|||
use App\Models\Model; |
|||
use Illuminate\Support\Facades\DB; |
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
|
|||
class Work extends Model |
|||
{ |
|||
use HasFactory; |
|||
protected $fillable = [ |
|||
'business_id', 'project_id', 'task_id', 'user_id', 'message', 'minute_sum', 'started_at', 'ended_at', |
|||
'task' |
|||
]; |
|||
|
|||
protected $fillable_relations = ['task']; |
|||
|
|||
protected $casts = [ |
|||
'started_at' => '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'); |
|||
} |
|||
} |
@ -0,0 +1,89 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use App\Models\Model; |
|||
|
|||
class Workflow extends Model |
|||
{ |
|||
protected $fillable = ['business_id', 'name', 'desc', 'statuses']; |
|||
|
|||
protected $reportable = [ |
|||
'name' |
|||
]; |
|||
|
|||
protected $fillable_relations = ['statuses']; |
|||
|
|||
public function rules() |
|||
{ |
|||
return [ |
|||
'name' => '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'); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
<?php |
|||
|
|||
namespace App\Notifications; |
|||
|
|||
use Illuminate\Bus\Queueable; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Notifications\Messages\MailMessage; |
|||
use Illuminate\Notifications\Notification; |
|||
|
|||
class DBNotification extends Notification |
|||
{ |
|||
use Queueable; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new notification instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->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'] |
|||
]; |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
<?php |
|||
|
|||
namespace App\Notifications; |
|||
|
|||
use App\Channels\Messages\FcmMessage; |
|||
use Illuminate\Bus\Queueable; |
|||
use Illuminate\Notifications\Notification; |
|||
|
|||
class FcmNotification extends Notification |
|||
{ |
|||
use Queueable; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* Create a new notification instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->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'], |
|||
]); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,78 @@ |
|||
<?php |
|||
|
|||
namespace App\Notifications; |
|||
|
|||
use Illuminate\Bus\Queueable; |
|||
use Illuminate\Contracts\Queue\ShouldQueue; |
|||
use Illuminate\Notifications\Messages\MailMessage; |
|||
use Illuminate\Notifications\Notification; |
|||
|
|||
class MailNotification extends Notification implements ShouldQueue |
|||
{ |
|||
use Queueable; |
|||
|
|||
public $message; |
|||
|
|||
/** |
|||
* The name of the queue connection to use when queueing the notification. |
|||
* |
|||
* @var string |
|||
*/ |
|||
// public $connection = 'redis';
|
|||
|
|||
/** |
|||
* If your queue connection's after_commit configuration option is set to true, |
|||
* indicate that a particular queued listener should be dispatched after all database transactions closed |
|||
* |
|||
* @var boolean |
|||
*/ |
|||
// public $afterCommit = true;
|
|||
|
|||
/** |
|||
* Determine which queues should be used for notification. |
|||
* |
|||
* @return string |
|||
*/ |
|||
// public $queue = 'mail-queue';
|
|||
|
|||
|
|||
|
|||
/** |
|||
* Create a new notification instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($message) |
|||
{ |
|||
$this->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('/')); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
namespace App\Rules; |
|||
|
|||
use Illuminate\Contracts\Validation\Rule; |
|||
|
|||
class MaxBound implements Rule |
|||
{ |
|||
/** |
|||
* Create a new rule instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Determine if the validation rule passes. |
|||
* |
|||
* @param string $attribute |
|||
* @param mixed $value |
|||
* @return bool |
|||
*/ |
|||
public function passes($attribute, $value) |
|||
{ |
|||
return sizeof(explode(',', trim($value, ','))) <= $this->bound; |
|||
} |
|||
|
|||
/** |
|||
* Get the validation error message. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function message() |
|||
{ |
|||
return 'The :attribute is out of bound.'; |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
namespace App\Utilities\Avatar; |
|||
|
|||
use Spatie\MediaLibrary\Conversions\Conversion; |
|||
use Spatie\MediaLibrary\MediaCollections\Models\Media; |
|||
use Spatie\MediaLibrary\Conversions\ConversionFileNamer; |
|||
|
|||
class DefaultConversionFileNamer extends ConversionFileNamer |
|||
{ |
|||
public function getFileName(Conversion $conversion, Media $media): string |
|||
{ |
|||
return $media->model->getKey(); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
namespace App\Utilities\Avatar; |
|||
|
|||
use Spatie\MediaLibrary\MediaCollections\Models\Media; |
|||
use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator; |
|||
|
|||
class DefaultPathGenerator implements PathGenerator |
|||
{ |
|||
/* |
|||
* Get a unique base path for the given media. |
|||
*/ |
|||
protected function getBasePath(Media $media): string |
|||
{ |
|||
return $media->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/'; |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
<?php |
|||
|
|||
namespace App\Utilities; |
|||
|
|||
use App\Models\User; |
|||
use Illuminate\Support\Arr; |
|||
use Jenssegers\Agent\Agent; |
|||
|
|||
/** |
|||
* @mixin Request |
|||
* Class RequestMixin |
|||
*/ |
|||
class BusinessInfoRequestMixin |
|||
{ |
|||
public function getBusinessInfo() |
|||
{ |
|||
return function () { |
|||
return Arr::get($this, '_business_info'); |
|||
}; |
|||
} |
|||
|
|||
|
|||
public function getFromBusinessInfo() |
|||
{ |
|||
return function(string $key) { |
|||
return Arr::get($this, "_business_info.$key"); |
|||
}; |
|||
} |
|||
|
|||
public function getBusinessUsers() |
|||
{ |
|||
return function() { |
|||
return $this->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'); |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
<?php |
|||
|
|||
namespace App\Utilities\Exceptions; |
|||
|
|||
use Throwable; |
|||
use ReflectionClass; |
|||
use ReflectionMethod; |
|||
use Illuminate\Support\Facades\Log; |
|||
use Illuminate\Validation\ValidationException; |
|||
use Illuminate\Auth\Access\AuthorizationException; |
|||
use Illuminate\Database\Eloquent\ModelNotFoundException; |
|||
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler; |
|||
use Symfony\Component\HttpKernel\Exception\HttpException; |
|||
|
|||
class Handler extends ExceptionHandler |
|||
{ |
|||
/** |
|||
* A list of the exception types that should not be reported. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $dontReport = [ |
|||
AuthorizationException::class, |
|||
HttpException::class, |
|||
ModelNotFoundException::class, |
|||
ValidationException::class, |
|||
]; |
|||
|
|||
public function report(Throwable $exception) |
|||
{ |
|||
// A trick that I took from Laravel macroable trait
|
|||
$methods = (new ReflectionClass($exception))->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); |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
<?php |
|||
|
|||
|
|||
namespace App\Utilities\HelperClass; |
|||
|
|||
|
|||
use App\Notifications\DBNotification; |
|||
use App\Notifications\FcmNotification; |
|||
use App\Notifications\MailNotification; |
|||
use Illuminate\Support\Facades\Notification; |
|||
|
|||
class NotificationHelper |
|||
{ |
|||
/** |
|||
* 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)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,27 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Support\Arr; |
|||
use Illuminate\Support\Str; |
|||
|
|||
if (! function_exists('enum')) { |
|||
function enum($key) |
|||
{ |
|||
// add a dot at the end of string to prevent undefined offset
|
|||
$key .= Str::of($key)->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; |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
<?php
use App\Jobs\AsyncCall;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\Request;
if (!function_exists('get')) {
function get(string $service, string $path, array $data, ?string $queue = null)
{
return call(Request::METHOD_GET, $service, $path, $data, $queue);
}
}
if (!function_exists('post')) {
function post(string $service, string $path, array $data, ?string $queue = null)
{
return call(Request::METHOD_POST, $service, $path, $data, $queue);
}
}
if (!function_exists('put')) {
function put(string $service, string $path, array $data, ?string $queue = null)
{
return call(Request::METHOD_PUT, $service, $path, $data, $queue);
}
}
if (!function_exists('delete')) {
function delete(string $service, string $path, array $data, ?string $queue = null)
{
return call(Request::METHOD_DELETE, $service, $path, $data, $queue);
}
}
if (!function_exists('call')) {
/**
* @return \Illuminate\Http\Client\Response
*/
function call(string $method, string $service, string $path, array $data, ?string $queue = null)
{
// token of this service for send data to other service
$token = 'YT76Nt2ofTbmkiP0ubvnlwOJLBtglA3UubjRhieTiTVP7jGPNX0RlueVOgIc';
// url for reaching the target service
$baseUrl = config("services.$service");
// create a pending request for this url
$pendingRequest = Http::retry(3, 100);
// if command data contain file, then it will be attached to the pending request
foreach ($data as $piece) {
if ($piece instanceof UploadedFile) {
$pendingRequest->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;
}
}
} |
@ -0,0 +1 @@ |
|||
<?php
use Illuminate\Support\Str;
$helpers = scandir(__DIR__);
foreach ($helpers as $helper) {
$filename = Str::of($helper)->trim(".");
if ($filename->isEmpty()) {
continue;
}
require_once (string) $filename;
} |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue