From eb9773b59c8b48e981636d9a100d830cd48a5c03 Mon Sep 17 00:00:00 2001 From: TemuulenBM Date: Wed, 18 Oct 2023 17:36:32 +0800 Subject: [PATCH 1/9] Adding notifications to fleetbase --- ...0_18_080950_create_notifications_table.php | 35 +++++++++++ src/Models/Notification.php | 27 +++++++++ src/Models/User.php | 10 ++++ src/Notifications/UserCreated.php | 59 ++++++++++++++++--- src/Observers/NotificationObserver.php | 9 +++ src/Observers/UserObserver.php | 6 +- src/Providers/CoreServiceProvider.php | 1 + 7 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 migrations/2023_10_18_080950_create_notifications_table.php create mode 100644 src/Models/Notification.php create mode 100644 src/Observers/NotificationObserver.php diff --git a/migrations/2023_10_18_080950_create_notifications_table.php b/migrations/2023_10_18_080950_create_notifications_table.php new file mode 100644 index 0000000..5f8c2dd --- /dev/null +++ b/migrations/2023_10_18_080950_create_notifications_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->string('type'); + $table->uuidMorphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('notifications'); + } +} diff --git a/src/Models/Notification.php b/src/Models/Notification.php new file mode 100644 index 0000000..9d7ec18 --- /dev/null +++ b/src/Models/Notification.php @@ -0,0 +1,27 @@ +message']; +} diff --git a/src/Models/User.php b/src/Models/User.php index 8ea78af..d61a7d9 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -477,6 +477,16 @@ public function routeNotificationForTwilio() return $this->phone; } + /** + * The channels the user receives notification broadcasts on. + * + * @return string + */ + public function receivesBroadcastNotificationsOn() + { + return 'user.' . $this->uuid; + } + /** * Accessor to get the types associated with the model instance. * diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index a2f1e02..f4ee3b5 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -5,16 +5,20 @@ use Fleetbase\Models\Company; use Fleetbase\Models\User; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; +// use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Messages\BroadcastMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Carbon; -class UserCreated extends Notification implements ShouldQueue +class UserCreated extends Notification { use Queueable; public ?User $user; public ?Company $company; + public ?string $sentAt; + public ?string $notificationId; /** * Create a new notification instance. @@ -25,6 +29,8 @@ public function __construct(User $user, Company $company) { $this->user = $user; $this->company = $company; + $this->sentAt = Carbon::now()->toDateTimeString(); + $this->notificationId = uniqid('notification_'); } /** @@ -34,7 +40,7 @@ public function __construct(User $user, Company $company) */ public function via($notifiable) { - return ['mail']; + return ['mail', 'database', 'broadcast']; } /** @@ -45,11 +51,46 @@ public function via($notifiable) public function toMail($notifiable) { return (new MailMessage()) - ->subject('🥳 New Fleetbase Signup!') - ->line('View user details below.') - ->line('Name: ' . $this->user->name) - ->line('Email: ' . $this->user->email) - ->line('Phone: ' . $this->user->phone) - ->line('Company: ' . $this->company->name); + ->subject('🥳 New Fleetbase Signup!') + ->line('View user details below.') + ->line('Name: ' . $this->user->name) + ->line('Email: ' . $this->user->email) + ->line('Phone: ' . $this->user->phone) + ->line('Company: ' . $this->company->name); + } + + /** + * Get the broadcastable representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\BroadcastMessage + */ + public function toBroadcast($notifiable) + { + return new BroadcastMessage($this->toArray($notifiable)); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + 'id' => $this->notificationId, + 'created_at' => $this->sentAt, + 'notifiable' => $notifiable->{$notifiable->getKeyName()}, + 'data' => [ + 'subject' => '🥳 New Fleetbase Signup!', + 'message' => 'New user ' . $this->user->name . ' added to organization ' . $this->company->name, + 'id' => $this->user->uuid, + 'email' => $this->user->email, + 'phone' => $this->user->phone, + 'companyId' => $this->company->uuid, + 'company' => $this->company->name, + ], + ]; } } diff --git a/src/Observers/NotificationObserver.php b/src/Observers/NotificationObserver.php new file mode 100644 index 0000000..e5429d1 --- /dev/null +++ b/src/Observers/NotificationObserver.php @@ -0,0 +1,9 @@ +load(['company']); + $user->load(['company.owner']); // create company user record if ($user->company_uuid) { @@ -25,6 +26,9 @@ public function created(User $user) // invite user to join company $user->sendInviteFromCompany(); + + // Notify the company owner a user has been created + $user->company->owner->notify(new UserCreated($user, $user->company)); } /** diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 7e9c0f3..79429f9 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -26,6 +26,7 @@ class CoreServiceProvider extends ServiceProvider public $observers = [ \Fleetbase\Models\User::class => \Fleetbase\Observers\UserObserver::class, \Fleetbase\Models\ApiCredential::class => \Fleetbase\Observers\ApiCredentialObserver::class, + \Fleetbase\Models\Notification::class => \Fleetbase\Observers\NotificationObserver::class, \Spatie\Activitylog\Models\Activity::class => \Fleetbase\Observers\ActivityObserver::class, ]; From 56daee0582586ce4bc5307b86d0a2bf086b75cef Mon Sep 17 00:00:00 2001 From: Temuulen Bayanmunkh Date: Fri, 20 Oct 2023 18:04:36 +0800 Subject: [PATCH 2/9] working notifications --- .../Internal/v1/NotificationController.php | 15 +++++++++++ src/Http/Filter/NotificationFilter.php | 26 +++++++++++++++++++ src/Models/Notification.php | 6 ++--- src/Notifications/UserCreated.php | 21 +++++++-------- src/routes.php | 1 + 5 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 src/Http/Controllers/Internal/v1/NotificationController.php create mode 100644 src/Http/Filter/NotificationFilter.php diff --git a/src/Http/Controllers/Internal/v1/NotificationController.php b/src/Http/Controllers/Internal/v1/NotificationController.php new file mode 100644 index 0000000..57947f1 --- /dev/null +++ b/src/Http/Controllers/Internal/v1/NotificationController.php @@ -0,0 +1,15 @@ +builder->where(function ($q) { + $q->where('notifiable_id', $this->session->get('company')); + $q->orWhere('notifiable_id', $this->session->get('user')); + }); + } + + public function query(?string $query) + { + $this->builder->search($query); + } + + public function unread(?bool $unread) + { + if ($unread) { + $this->builder->whereNull('read_at'); + } + } +} diff --git a/src/Models/Notification.php b/src/Models/Notification.php index 9d7ec18..595e847 100644 --- a/src/Models/Notification.php +++ b/src/Models/Notification.php @@ -2,21 +2,21 @@ namespace Fleetbase\Models; +use Fleetbase\Traits\Filterable; use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\Searchable; use Illuminate\Notifications\DatabaseNotification; class Notification extends DatabaseNotification { - use HasApiModelBehavior; - use Searchable; + use HasApiModelBehavior, Searchable, Filterable; /** * The attributes that are mass assignable. * * @var array */ - protected $fillable = ['read_at', 'data']; + protected $fillable = ['read_at', 'data', 'notifiable_id', 'notifiable_type', 'type']; /** * The searchable columns. diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index f4ee3b5..80b5e27 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -79,18 +79,15 @@ public function toBroadcast($notifiable) public function toArray($notifiable) { return [ - 'id' => $this->notificationId, - 'created_at' => $this->sentAt, - 'notifiable' => $notifiable->{$notifiable->getKeyName()}, - 'data' => [ - 'subject' => '🥳 New Fleetbase Signup!', - 'message' => 'New user ' . $this->user->name . ' added to organization ' . $this->company->name, - 'id' => $this->user->uuid, - 'email' => $this->user->email, - 'phone' => $this->user->phone, - 'companyId' => $this->company->uuid, - 'company' => $this->company->name, - ], + 'notification_id' => $this->notificationId, + 'sent_at' => $this->sentAt, + 'subject' => '🥳 New Fleetbase Signup!', + 'message' => 'New user ' . $this->user->name . ' added to organization ' . $this->company->name, + 'id' => $this->user->uuid, + 'email' => $this->user->email, + 'phone' => $this->user->phone, + 'companyId' => $this->company->uuid, + 'company' => $this->company->name, ]; } } diff --git a/src/routes.php b/src/routes.php index dae1ec4..74dea65 100644 --- a/src/routes.php +++ b/src/routes.php @@ -157,6 +157,7 @@ function ($router, $controller) { } ); $router->fleetbaseRoutes('transactions'); + $router->fleetbaseRoutes('notifications'); } ); } From 7684050586840d71b5d4cfcf44a95f2e694137bc Mon Sep 17 00:00:00 2001 From: TemuulenBM Date: Thu, 26 Oct 2023 11:25:32 +0800 Subject: [PATCH 3/9] created notification registry --- .../Internal/v1/NotificationController.php | 104 +++++++++++++++++- src/Models/Notification.php | 27 +++++ src/Notifications/NotificationRegistry.php | 61 ++++++++++ src/Notifications/UserCreated.php | 3 + src/Providers/CoreServiceProvider.php | 7 ++ src/routes.php | 7 +- 6 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/Notifications/NotificationRegistry.php diff --git a/src/Http/Controllers/Internal/v1/NotificationController.php b/src/Http/Controllers/Internal/v1/NotificationController.php index 57947f1..8b748c0 100644 --- a/src/Http/Controllers/Internal/v1/NotificationController.php +++ b/src/Http/Controllers/Internal/v1/NotificationController.php @@ -3,7 +3,12 @@ namespace Fleetbase\Http\Controllers\Internal\v1; use Fleetbase\Http\Controllers\FleetbaseController; +use Fleetbase\Models\Notification; +use Illuminate\Http\Request; +/** + * Controller for managing notifications. + */ class NotificationController extends FleetbaseController { /** @@ -12,4 +17,101 @@ class NotificationController extends FleetbaseController * @var string */ public $resource = 'notification'; -} \ No newline at end of file + + /** + * Receives an array of ID's for notifications which should be marked as read. + * + * @param \Illuminate\Http\Request $request The HTTP request object. + * @return \Illuminate\Http\Response The HTTP response. + */ + public function markAsRead(Request $request) + { + $notifications = $request->input('notifications'); + $total = count($notifications); + $read = []; + + foreach ($notifications as $id) { + $notification = Notification::where('id', $id)->first(); + + if ($notification) { + $read[] = $notification->markAsRead(); + } + } + + return response()->json([ + 'status' => 'ok', + 'message' => 'Notifications marked as read', + 'marked_as_read' => count($read), + 'total' => $total + ]); + } + + /** + * Receives an array of ID's for notifications which should be marked as read. + * + * @return \Illuminate\Http\Response The HTTP response. + */ + public function markAllAsRead() + { + $notifications = Notification::where('notifiable_id', session('user'))->get(); + + foreach ($notifications as $notification) { + $notification->markAsRead(); + } + + return response()->json([ + 'status' => 'ok', + 'message' => 'All notifications marked as read', + ]); + } + + /** + * Deletes a single notification. + * + * @param int $notificationId The ID of the notification to delete. + * @return \Illuminate\Http\Response The HTTP response. + */ + public function deleteNotification($notificationId) + { + $notification = Notification::find($notificationId); + + if ($notification) { + $notification->deleteNotification(); + return response()->json(['message' => 'Notification deleted successfully'], 200); + } else { + return response()->json(['error' => 'Notification not found'], 404); + } + } + + /** + * Deletes all notifications for the authenticated user. + * + * @param \Illuminate\Http\Request $request The HTTP request object. + * @return \Illuminate\Http\Response The HTTP response. + */ + public function bulkDelete(Request $request) + { + $notifications = $request->input('notifications'); + + if (empty($notifications)) { + Notification::where('notifiable_id', session('user'))->delete(); + } else { + Notification::whereIn('id', $notifications)->delete(); + } + + return response()->json([ + 'status' => 'ok', + 'message' => 'Selected notifications deleted successfully', + ]); + } + + /** + * Get the list of registered notifications from the NotificationRegistry. + * + * @return \Illuminate\Http\JsonResponse The JSON response containing registered notifications. + */ + public function registry() + { + return response()->json(\Fleetbase\Notifications\NotificationRegistry::$notifications); + } +} diff --git a/src/Models/Notification.php b/src/Models/Notification.php index 595e847..a76b93d 100644 --- a/src/Models/Notification.php +++ b/src/Models/Notification.php @@ -24,4 +24,31 @@ class Notification extends DatabaseNotification * @var array */ protected $searchableColumns = ['data->message']; + + /** + * Marks the notification as read. + * + * @param boolean $save + * @return \Fleetbase\Models\Notification + */ + public function markAsRead($save = true): Notification + { + $this->read_at = now(); + + if ($save) { + $this->save(); + } + + return $this; + } + /** + * Delete the notification. + * + * @return void + */ + public function deleteNotification() + { + $this->delete(); + } + } diff --git a/src/Notifications/NotificationRegistry.php b/src/Notifications/NotificationRegistry.php new file mode 100644 index 0000000..62043b3 --- /dev/null +++ b/src/Notifications/NotificationRegistry.php @@ -0,0 +1,61 @@ + $notificationClass, + 'name' => static::getNotificationClassProperty($notificationClass, 'name'), + 'description' => static::getNotificationClassProperty($notificationClass, 'description'), + 'package' => static::getNotificationClassProperty($notificationClass, 'package'), + 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions'), + ]; + } + + /** + * Get a property of a notification class. + * + * @param string $notificationClass The class name. + * @param string $property The name of the property to retrieve. + * @return mixed|null The value of the property or null if not found. + */ + private static function getNotificationClassProperty(string $notificationClass, string $property) + { + if (!class_exists($notificationClass) || !property_exists($notificationClass, $property)) { + return null; + } + + $properties = get_class_vars($notificationClass); + return data_get($properties, $property); + } +} diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index 80b5e27..e623295 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -19,6 +19,9 @@ class UserCreated extends Notification public ?Company $company; public ?string $sentAt; public ?string $notificationId; + public static string $name = 'User Created'; + public static string $description = 'Notify when a new user has been created.'; + public static string $package = 'core'; /** * Create a new notification instance. diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 79429f9..c9843bc 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -3,6 +3,7 @@ namespace Fleetbase\Providers; use Fleetbase\Models\Setting; +use Fleetbase\Notifications\NotificationRegistry; use Fleetbase\Support\Expansion; use Fleetbase\Support\Utils; use Illuminate\Console\Scheduling\Schedule; @@ -78,6 +79,7 @@ public function boot() $this->registerObservers(); $this->registerExpansionsFrom(); $this->registerMiddleware(); + $this->registerNotifications(); $this->loadRoutesFrom(__DIR__ . '/../routes.php'); $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); @@ -344,6 +346,11 @@ function ($ns) use ($className) { } } + private function registerNotifications() + { + NotificationRegistry::register(\Fleetbase\Notifications\UserCreated::class); + } + /** * Register the middleware groups defined by the service provider. */ diff --git a/src/routes.php b/src/routes.php index 74dea65..31b1141 100644 --- a/src/routes.php +++ b/src/routes.php @@ -157,7 +157,12 @@ function ($router, $controller) { } ); $router->fleetbaseRoutes('transactions'); - $router->fleetbaseRoutes('notifications'); + $router->fleetbaseRoutes('notifications', function ($router, $controller) { + $router->get('registry', $controller('registry')); + $router->put('mark-as-read', $controller('markAsRead')); + $router->put('mark-all-read', $controller('markAllAsRead')); + $router->delete('bulk-delete', $controller('bulkDelete')); + }); } ); } From fc8f32c0b4a77971001c7deb324147c74ca2a950 Mon Sep 17 00:00:00 2001 From: TemuulenBM Date: Mon, 30 Oct 2023 12:02:51 +0800 Subject: [PATCH 4/9] latest for notifications backend implementation --- .../Internal/v1/NotificationController.php | 52 +++++++++- src/Models/Company.php | 2 + src/Models/Group.php | 2 + src/Models/Role.php | 2 + src/Notifications/NotificationRegistry.php | 94 ++++++++++++++++++- src/routes.php | 3 + 6 files changed, 149 insertions(+), 6 deletions(-) diff --git a/src/Http/Controllers/Internal/v1/NotificationController.php b/src/Http/Controllers/Internal/v1/NotificationController.php index 8b748c0..b53a668 100644 --- a/src/Http/Controllers/Internal/v1/NotificationController.php +++ b/src/Http/Controllers/Internal/v1/NotificationController.php @@ -4,6 +4,8 @@ use Fleetbase\Http\Controllers\FleetbaseController; use Fleetbase\Models\Notification; +use Fleetbase\Models\Setting; +use Fleetbase\Notifications\NotificationRegistry; use Illuminate\Http\Request; /** @@ -108,10 +110,56 @@ public function bulkDelete(Request $request) /** * Get the list of registered notifications from the NotificationRegistry. * - * @return \Illuminate\Http\JsonResponse The JSON response containing registered notifications. + * @return \Illuminate\Http\JsonResponse The JSON response */ public function registry() { - return response()->json(\Fleetbase\Notifications\NotificationRegistry::$notifications); + return response()->json(NotificationRegistry::$notifications); + } + + public function notifiables() + { + return response()->json(NotificationRegistry::getNotifiables()); + } + + /** + * Save user notification settings. + * + * + * @param Request $request The HTTP request object containing the notification settings data. + * + * @throws \Exception If the provided notification settings data is not an array. + * + * @return \Illuminate\Http\JsonResponse A JSON response. + */ + public function saveSettings(Request $request) + { + $notificationSettings = $request->input('notificationSettings'); + if (!is_array($notificationSettings)) { + throw new \Exception('Invalid notification settings data.'); + } + Setting::configure('notification_settings', $notificationSettings); + + return response()->json([ + 'status' => 'ok', + 'message' => 'Notification settings succesfully saved.', + ]); + } + + /** + * Retrieve and return the notification settings for the user. + * + * + * @return \Illuminate\Http\JsonResponse + */ + public function getSettings() + { + $notificationSettings = Setting::lookup('notification_settings'); + + return response()->json([ + 'status' => 'ok', + 'message' => 'Notification settings successfully fetched.', + 'notificationSettings' => $notificationSettings, + ]); } } diff --git a/src/Models/Company.php b/src/Models/Company.php index 1bba48f..07b0439 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -10,6 +10,7 @@ use Fleetbase\Traits\Searchable; use Fleetbase\Traits\SendsWebhooks; use Fleetbase\Traits\TracksApiCredential; +use Illuminate\Notifications\Notifiable; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; @@ -23,6 +24,7 @@ class Company extends Model use HasSlug; use Searchable; use SendsWebhooks; + use Notifiable; /** * The database connection to use. diff --git a/src/Models/Group.php b/src/Models/Group.php index 2e1bff9..109bc34 100644 --- a/src/Models/Group.php +++ b/src/Models/Group.php @@ -6,6 +6,7 @@ use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\HasPolicies; use Fleetbase\Traits\HasUuid; +use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasPermissions; use Spatie\Permission\Traits\HasRoles; use Spatie\Sluggable\HasSlug; @@ -20,6 +21,7 @@ class Group extends Model use HasRoles; use HasSlug; use Filterable; + use Notifiable; /** * The database connection to use. diff --git a/src/Models/Role.php b/src/Models/Role.php index 739a954..d2a0c93 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -7,6 +7,7 @@ use Fleetbase\Traits\HasPolicies; use Fleetbase\Traits\HasUuid; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Notifications\Notifiable; use Spatie\Permission\Models\Role as BaseRole; class Role extends BaseRole @@ -16,6 +17,7 @@ class Role extends BaseRole use SoftDeletes; use HasPolicies; use Filterable; + use Notifiable; /** * The database connection to use. diff --git a/src/Notifications/NotificationRegistry.php b/src/Notifications/NotificationRegistry.php index 62043b3..89bc162 100644 --- a/src/Notifications/NotificationRegistry.php +++ b/src/Notifications/NotificationRegistry.php @@ -2,6 +2,10 @@ namespace Fleetbase\Notifications; +use Fleetbase\Support\Utils; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Str; + /** * Notification Registry for managing registered notifications. */ @@ -14,14 +18,23 @@ class NotificationRegistry */ public static $notifications = []; + /** + * Array to store registered notificable types. + * + * @var array + */ + public static $notifiables = [ + \Fleetbase\Models\User::class, + \Fleetbase\Models\Group::class, + \Fleetbase\Models\Role::class, + \Fleetbase\Models\Company::class, + ]; + /** * Register a notification. * * @param string|array $notificationClass The class or an array of classes to register. - * @param string $name The name of the notification. - * @param string $description The description of the notification. - * @param string $package The package to which the notification belongs. - * @param array $options Additional options for the notification. + * @return void */ public static function register($notificationClass): void { @@ -42,6 +55,25 @@ public static function register($notificationClass): void ]; } + /** + * Register a notifiable. + * + * @param string|array $notifiableClass The class of the notifiable. + * @return void + */ + public static function registerNotifiable($notifiableClass): void + { + if (is_array($notifiableClass)) { + foreach ($notifiableClass as $notifiableClassElement) { + static::registerNotifiable($notifiableClassElement); + } + + return; + } + + static::$notifiables[] = $notifiableClass; + } + /** * Get a property of a notification class. * @@ -58,4 +90,58 @@ private static function getNotificationClassProperty(string $notificationClass, $properties = get_class_vars($notificationClass); return data_get($properties, $property); } + + /** + * Get all notificables for a company. + * + * @return array + */ + public static function getNotifiablesForCompany(string $companyId): array + { + $companySessionId = $companyId; + + // if no company session provided, no notifiables + if (!$companySessionId) { + return []; + } + + $notifiables = []; + + // iterate through each notifiable types and get records + foreach (static::$notifiables as $notifiableClass) { + $notifiableModel = app($notifiableClass); + $type = class_basename($notifiableClass); + + if ($notifiableModel && $notifiableModel instanceof \Illuminate\Database\Eloquent\Model) { + $table = $notifiableModel->getTable(); + $hasCompanyColumn = Schema::hasColumn($table, 'company_uuid'); + + if ($hasCompanyColumn) { + $records = $notifiableModel->where('company_uuid', $companySessionId)->get(); + + foreach ($records as $record) { + $recordId = Utils::or($record, ['uuid', 'id']); + $notifiables[] = [ + 'label' => Str::title($type) . ': ' . Utils::or($record, ['name', 'public_id']), + 'uuid' => $recordId, + 'value' => strtolower($type) . ':' . $recordId, + ]; + } + } + } + } + + return $notifiables; + } + + /** + * Gets all notifiables for the current company session. + * + * @return array + */ + public static function getNotifiables(): array + { + $companySessionId = session('company'); + return static::getNotifiablesForCompany($companySessionId); + } } diff --git a/src/routes.php b/src/routes.php index 31b1141..32f93a2 100644 --- a/src/routes.php +++ b/src/routes.php @@ -159,9 +159,12 @@ function ($router, $controller) { $router->fleetbaseRoutes('transactions'); $router->fleetbaseRoutes('notifications', function ($router, $controller) { $router->get('registry', $controller('registry')); + $router->get('notifiables', $controller('notifiables')); + $router->get('get-settings', $controller('getSettings')); $router->put('mark-as-read', $controller('markAsRead')); $router->put('mark-all-read', $controller('markAllAsRead')); $router->delete('bulk-delete', $controller('bulkDelete')); + $router->post('save-settings', $controller('saveSettings')); }); } ); From 6b9255def71e17c4683ce04deb26fad48893ff48 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 30 Oct 2023 14:54:00 +0800 Subject: [PATCH 5/9] Moved `NotificationRegistry` namespace and implemented `notify()` to use notification settings --- .../Internal/v1/NotificationController.php | 2 +- src/Models/Group.php | 20 ++ src/Notifications/NotificationRegistry.php | 147 ---------- src/Notifications/UserCreated.php | 4 +- src/Observers/UserObserver.php | 4 +- src/Providers/CoreServiceProvider.php | 2 +- src/Support/NotificationRegistry.php | 250 ++++++++++++++++++ 7 files changed, 277 insertions(+), 152 deletions(-) delete mode 100644 src/Notifications/NotificationRegistry.php create mode 100644 src/Support/NotificationRegistry.php diff --git a/src/Http/Controllers/Internal/v1/NotificationController.php b/src/Http/Controllers/Internal/v1/NotificationController.php index b53a668..b7f7b60 100644 --- a/src/Http/Controllers/Internal/v1/NotificationController.php +++ b/src/Http/Controllers/Internal/v1/NotificationController.php @@ -5,7 +5,7 @@ use Fleetbase\Http\Controllers\FleetbaseController; use Fleetbase\Models\Notification; use Fleetbase\Models\Setting; -use Fleetbase\Notifications\NotificationRegistry; +use Fleetbase\Support\NotificationRegistry; use Illuminate\Http\Request; /** diff --git a/src/Models/Group.php b/src/Models/Group.php index 109bc34..e17144b 100644 --- a/src/Models/Group.php +++ b/src/Models/Group.php @@ -12,6 +12,9 @@ use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; +/** + * @property \Illuminate\Database\Eloquent\Collection $users + */ class Group extends Model { use HasUuid; @@ -58,6 +61,13 @@ class Group extends Model */ protected $with = ['users', 'permissions', 'policies']; + /** + * The relationship of the multiple notifiables. + * + * @var string + */ + public string $containsMultipleNotifiables = 'users'; + /** * Get the options for generating the slug. */ @@ -77,4 +87,14 @@ public function users() { return $this->hasManyThrough(User::class, GroupUser::class, 'group_uuid', 'uuid', 'uuid', 'user_uuid'); } + + /** + * An array of each group members email to send notification emails to. + * + * @return \Illuminate\Support\Collection + */ + public function routeNotificationForMail(): \Illuminate\Support\Collection + { + return $this->users->pluck('email'); + } } diff --git a/src/Notifications/NotificationRegistry.php b/src/Notifications/NotificationRegistry.php deleted file mode 100644 index 89bc162..0000000 --- a/src/Notifications/NotificationRegistry.php +++ /dev/null @@ -1,147 +0,0 @@ - $notificationClass, - 'name' => static::getNotificationClassProperty($notificationClass, 'name'), - 'description' => static::getNotificationClassProperty($notificationClass, 'description'), - 'package' => static::getNotificationClassProperty($notificationClass, 'package'), - 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions'), - ]; - } - - /** - * Register a notifiable. - * - * @param string|array $notifiableClass The class of the notifiable. - * @return void - */ - public static function registerNotifiable($notifiableClass): void - { - if (is_array($notifiableClass)) { - foreach ($notifiableClass as $notifiableClassElement) { - static::registerNotifiable($notifiableClassElement); - } - - return; - } - - static::$notifiables[] = $notifiableClass; - } - - /** - * Get a property of a notification class. - * - * @param string $notificationClass The class name. - * @param string $property The name of the property to retrieve. - * @return mixed|null The value of the property or null if not found. - */ - private static function getNotificationClassProperty(string $notificationClass, string $property) - { - if (!class_exists($notificationClass) || !property_exists($notificationClass, $property)) { - return null; - } - - $properties = get_class_vars($notificationClass); - return data_get($properties, $property); - } - - /** - * Get all notificables for a company. - * - * @return array - */ - public static function getNotifiablesForCompany(string $companyId): array - { - $companySessionId = $companyId; - - // if no company session provided, no notifiables - if (!$companySessionId) { - return []; - } - - $notifiables = []; - - // iterate through each notifiable types and get records - foreach (static::$notifiables as $notifiableClass) { - $notifiableModel = app($notifiableClass); - $type = class_basename($notifiableClass); - - if ($notifiableModel && $notifiableModel instanceof \Illuminate\Database\Eloquent\Model) { - $table = $notifiableModel->getTable(); - $hasCompanyColumn = Schema::hasColumn($table, 'company_uuid'); - - if ($hasCompanyColumn) { - $records = $notifiableModel->where('company_uuid', $companySessionId)->get(); - - foreach ($records as $record) { - $recordId = Utils::or($record, ['uuid', 'id']); - $notifiables[] = [ - 'label' => Str::title($type) . ': ' . Utils::or($record, ['name', 'public_id']), - 'uuid' => $recordId, - 'value' => strtolower($type) . ':' . $recordId, - ]; - } - } - } - } - - return $notifiables; - } - - /** - * Gets all notifiables for the current company session. - * - * @return array - */ - public static function getNotifiables(): array - { - $companySessionId = session('company'); - return static::getNotifiablesForCompany($companySessionId); - } -} diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index e623295..7cf119f 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -5,13 +5,13 @@ use Fleetbase\Models\Company; use Fleetbase\Models\User; use Illuminate\Bus\Queueable; -// use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\BroadcastMessage; use Illuminate\Notifications\Notification; use Illuminate\Support\Carbon; -class UserCreated extends Notification +class UserCreated extends Notification implements ShouldQueue { use Queueable; diff --git a/src/Observers/UserObserver.php b/src/Observers/UserObserver.php index 6f434b5..6555bde 100644 --- a/src/Observers/UserObserver.php +++ b/src/Observers/UserObserver.php @@ -6,6 +6,7 @@ use Fleetbase\Models\CompanyUser; use Fleetbase\Models\User; use Fleetbase\Notifications\UserCreated; +use Fleetbase\Support\NotificationRegistry; class UserObserver { @@ -28,7 +29,8 @@ public function created(User $user) $user->sendInviteFromCompany(); // Notify the company owner a user has been created - $user->company->owner->notify(new UserCreated($user, $user->company)); + // $user->company->owner->notify(new UserCreated($user, $user->company)); + NotificationRegistry::notify(UserCreated::class, $user, $user->company); } /** diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index c9843bc..9187265 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -3,7 +3,7 @@ namespace Fleetbase\Providers; use Fleetbase\Models\Setting; -use Fleetbase\Notifications\NotificationRegistry; +use Fleetbase\Support\NotificationRegistry; use Fleetbase\Support\Expansion; use Fleetbase\Support\Utils; use Illuminate\Console\Scheduling\Schedule; diff --git a/src/Support/NotificationRegistry.php b/src/Support/NotificationRegistry.php new file mode 100644 index 0000000..b52f5d4 --- /dev/null +++ b/src/Support/NotificationRegistry.php @@ -0,0 +1,250 @@ + $notificationClass, + 'name' => static::getNotificationClassProperty($notificationClass, 'name'), + 'description' => static::getNotificationClassProperty($notificationClass, 'description'), + 'package' => static::getNotificationClassProperty($notificationClass, 'package'), + 'params' => static::getNotificationClassParameters($notificationClass), + 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions', []), + ]; + } + + /** + * Register a notifiable. + * + * @param string|array $notifiableClass The class of the notifiable. + * @return void + */ + public static function registerNotifiable($notifiableClass): void + { + if (is_array($notifiableClass)) { + foreach ($notifiableClass as $notifiableClassElement) { + static::registerNotifiable($notifiableClassElement); + } + + return; + } + + static::$notifiables[] = $notifiableClass; + } + + /** + * Get a property of a notification class. + * + * @param string $notificationClass The class name. + * @param string $property The name of the property to retrieve. + * @param mixed $defaultValue The default value if the property is not found. + * @return mixed|null The value of the property or null if not found. + */ + private static function getNotificationClassProperty(string $notificationClass, string $property, $defaultValue = null) + { + if (!class_exists($notificationClass) || !property_exists($notificationClass, $property)) { + return $defaultValue; + } + + $properties = get_class_vars($notificationClass); + return data_get($properties, $property, $defaultValue); + } + + private function getNotificationClassParameters(string $notificationClass): array + { + // Make sure class exists + if (!class_exists($notificationClass)) { + return []; + } + + // Create ReflectionMethod object for the constructor + $reflection = new \ReflectionMethod($notificationClass, '__construct'); + + // Get parameters + $params = $reflection->getParameters(); + + // Array to store required parameters + $requiredParams = []; + + foreach ($params as $param) { + // Get parameter name + $name = $param->getName(); + + // Get parameter type + $type = $param->getType(); + + // Check if the parameter is optional + $isOptional = $param->isOptional(); + + $requiredParams[] = [ + 'name' => $name, + 'type' => $type ? $type->getName() : 'mixed', // If type is null, set it as 'mixed' + 'optional' => $isOptional + ]; + } + + return $requiredParams; + } + + /** + * Get all notificables for a company. + * + * @return array + */ + public static function getNotifiablesForCompany(string $companyId): array + { + $companySessionId = $companyId; + + // if no company session provided, no notifiables + if (!$companySessionId) { + return []; + } + + $notifiables = []; + + // iterate through each notifiable types and get records + foreach (static::$notifiables as $notifiableClass) { + $notifiableModel = app($notifiableClass); + $type = class_basename($notifiableClass); + + if ($notifiableModel && $notifiableModel instanceof \Illuminate\Database\Eloquent\Model) { + $table = $notifiableModel->getTable(); + $modelClass = get_class($notifiableModel); + $hasCompanyColumn = Schema::hasColumn($table, 'company_uuid'); + + if ($hasCompanyColumn) { + $records = $notifiableModel->where('company_uuid', $companySessionId)->get(); + + foreach ($records as $record) { + $recordId = Utils::or($record, ['uuid', 'id']); + $notifiables[] = [ + 'label' => Str::title($type) . ': ' . Utils::or($record, ['name', 'public_id']), + 'key' => $recordId, + 'primaryKey' => $notifiableModel->getKeyName(), + 'definition' => $modelClass, + 'value' => Str::slug(str_replace('\\', '-', $modelClass)) . ':' . $recordId, + ]; + } + } + } + } + + return $notifiables; + } + + /** + * Gets all notifiables for the current company session. + * + * @return array + */ + public static function getNotifiables(): array + { + $companySessionId = session('company'); + return static::getNotifiablesForCompany($companySessionId); + } + + public static function notify($notificationClass, ...$params) + { + // if the class doesn't exist return false + if (!class_exists($notificationClass)) { + return false; + } + + + // resolve settings for notification + $notificationSettings = Setting::lookup('notification_settings'); + $notificationSettingsKey = Str::camel(str_replace('\\', '', $notificationClass)); + + // get the notification settings for this $notificationClass + $settings = data_get($notificationSettings, $notificationSettingsKey, []); + + // if we have the settings resolve the notifiables + if ($settings) { + $notifiables = data_get($settings, 'notifiables', []); + + if (is_array($notifiables)) { + foreach ($notifiables as $notifiable) { + $notifiableModel = static::resolveNotifiable($notifiable); + + // if has multiple notifiables + if (isset($notifiableModel->containsMultipleNotifiables) && is_string($notifiableModel->containsMultipleNotifiables)) { + $notifiablesRelationship = $notifiableModel->containsMultipleNotifiables; + $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); + + // notifiy each + foreach ($multipleNotifiables as $singleNotifiable) { + $singleNotifiable->notify(new $notificationClass(...$params)); + } + + // continue + continue; + } + + if ($notifiableModel) { + $notifiableModel->notify(new $notificationClass(...$params)); + } + } + } + } + } + + protected static function resolveNotifiable(array $notifiableObject): ?\Illuminate\Database\Eloquent\Model + { + $definition = data_get($notifiableObject, 'definition'); + $primaryKey = data_get($notifiableObject, 'primaryKey'); + $key = data_get($notifiableObject, 'key'); + + // resolve the notifiable + $modelInstance = app($definition); + + if ($modelInstance instanceof \Illuminate\Database\Eloquent\Model) { + return $modelInstance->where($primaryKey, $key)->first(); + } + + return null; + } +} From 9a9d7ef15103cee31aba2ad9306dbb4ccc1c152c Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 30 Oct 2023 15:23:36 +0800 Subject: [PATCH 6/9] hotfix the `consoleUrl` utility function --- src/Support/NotificationRegistry.php | 31 ++++++++++++++++++++++++---- src/Support/Utils.php | 4 +++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Support/NotificationRegistry.php b/src/Support/NotificationRegistry.php index b52f5d4..29d5d87 100644 --- a/src/Support/NotificationRegistry.php +++ b/src/Support/NotificationRegistry.php @@ -94,6 +94,13 @@ private static function getNotificationClassProperty(string $notificationClass, return data_get($properties, $property, $defaultValue); } + /** + * Get the parameters required by a specific notification class constructor. + * + * @param string $notificationClass The class name of the notification. + * + * @return array An array of associative arrays, each containing details about a parameter required by the constructor. + */ private function getNotificationClassParameters(string $notificationClass): array { // Make sure class exists @@ -187,14 +194,22 @@ public static function getNotifiables(): array return static::getNotifiablesForCompany($companySessionId); } - public static function notify($notificationClass, ...$params) + /** + * Notify one or multiple notifiables using a specific notification class. + * + * @param string $notificationClass The class name of the notification to use. + * @param mixed ...$params Additional parameters for the notification class. + * + * @return void + * @throws \Exception + */ + public static function notify($notificationClass, ...$params): void { // if the class doesn't exist return false if (!class_exists($notificationClass)) { - return false; + return; } - // resolve settings for notification $notificationSettings = Setting::lookup('notification_settings'); $notificationSettingsKey = Str::camel(str_replace('\\', '', $notificationClass)); @@ -219,7 +234,7 @@ public static function notify($notificationClass, ...$params) foreach ($multipleNotifiables as $singleNotifiable) { $singleNotifiable->notify(new $notificationClass(...$params)); } - + // continue continue; } @@ -232,6 +247,14 @@ public static function notify($notificationClass, ...$params) } } + + /** + * Resolve a notifiable object to an Eloquent model. + * + * @param array $notifiableObject An associative array containing the definition and primary key to resolve the notifiable object. + * + * @return \Illuminate\Database\Eloquent\Model|null The Eloquent model or null if it cannot be resolved. + */ protected static function resolveNotifiable(array $notifiableObject): ?\Illuminate\Database\Eloquent\Model { $definition = data_get($notifiableObject, 'definition'); diff --git a/src/Support/Utils.php b/src/Support/Utils.php index 38a65c1..2db5309 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -43,7 +43,9 @@ public static function consoleUrl(string $path, ?array $queryParams = [], $subdo $segments = []; // check if using secure console - $segments[] = config('fleetbase.console.secure', !$isLocalDevelopment) ? 'https://' : 'http://'; + if (!Str::startsWith($host, 'http')) { + $segments[] = config('fleetbase.console.secure', !$isLocalDevelopment) ? 'https://' : 'http://'; + } // check for subdomain if (config('fleetbase.console.subdomain', $subdomain)) { From 334a5c5b9b8f1e13b0cc2a604bcc6ac8d87161a8 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 30 Oct 2023 18:05:38 +0800 Subject: [PATCH 7/9] Notifications setup and working for BE --- .../Internal/v1/AuthController.php | 8 +- .../Internal/v1/UserController.php | 5 + src/Notifications/PasswordReset.php | 51 ++++++++- src/Notifications/PasswordResetRequest.php | 56 ---------- .../UserAcceptedCompanyInvite.php | 67 +++++++++++- src/Notifications/UserCreated.php | 64 +++++++++-- src/Notifications/UserForgotPassword.php | 12 +++ src/Notifications/UserInvited.php | 30 ++++++ src/Providers/CoreServiceProvider.php | 5 +- src/Support/NotificationRegistry.php | 101 ++++++++++++++++-- src/routes.php | 1 + 11 files changed, 316 insertions(+), 84 deletions(-) delete mode 100644 src/Notifications/PasswordResetRequest.php diff --git a/src/Http/Controllers/Internal/v1/AuthController.php b/src/Http/Controllers/Internal/v1/AuthController.php index 067c239..5dcb259 100644 --- a/src/Http/Controllers/Internal/v1/AuthController.php +++ b/src/Http/Controllers/Internal/v1/AuthController.php @@ -275,10 +275,12 @@ public function createPasswordReset(UserForgotPasswordRequest $request) */ public function resetPassword(ResetPasswordRequest $request) { - // get verification code $verificationCode = VerificationCode::where('code', $request->input('code'))->with(['subject'])->first(); + $link = $request->input('link'); + $password = $request->input('password'); - if ($verificationCode->uuid !== $request->input('link')) { + // If link isn't valid + if ($verificationCode->uuid !== $link) { return response()->error('Invalid password reset request!'); } @@ -288,7 +290,7 @@ public function resetPassword(ResetPasswordRequest $request) } // reset users password - $verificationCode->subject->changePassword($request->input('password')); + $verificationCode->subject->changePassword($password); // verify code by deleting so its unusable $verificationCode->delete(); diff --git a/src/Http/Controllers/Internal/v1/UserController.php b/src/Http/Controllers/Internal/v1/UserController.php index 75da093..108b951 100644 --- a/src/Http/Controllers/Internal/v1/UserController.php +++ b/src/Http/Controllers/Internal/v1/UserController.php @@ -14,7 +14,9 @@ use Fleetbase\Models\CompanyUser; use Fleetbase\Models\Invite; use Fleetbase\Models\User; +use Fleetbase\Notifications\UserAcceptedCompanyInvite; use Fleetbase\Notifications\UserInvited; +use Fleetbase\Support\NotificationRegistry; use Fleetbase\Support\Utils; use Illuminate\Http\Request; use Illuminate\Support\Arr; @@ -182,6 +184,9 @@ public function acceptCompanyInvite(AcceptCompanyInvite $request) // create authentication token for user $token = $user->createToken($invite->code); + // Notify company that user has accepted their invite + NotificationRegistry::notify(UserAcceptedCompanyInvite::class, $company, $user); + return response()->json([ 'status' => 'ok', 'token' => $token->plainTextToken, diff --git a/src/Notifications/PasswordReset.php b/src/Notifications/PasswordReset.php index 2e1ca3b..69f99cb 100644 --- a/src/Notifications/PasswordReset.php +++ b/src/Notifications/PasswordReset.php @@ -2,22 +2,62 @@ namespace Fleetbase\Notifications; +use Fleetbase\Models\VerificationCode; +use Fleetbase\Support\Utils; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\HtmlString; class PasswordReset extends Notification implements ShouldQueue { use Queueable; + /** + * Instance of the verification code for the password reset. + * + * @var \Fleetbase\Models\VerificationCode|null + */ + public ?VerificationCode $verificationCode; + + /** + * The URL where the user can reset their password. + * + * @var string + */ + public string $url; + + /** + * The notification name. + * + * @var string + */ + public static string $name = 'Password Reset'; + + /** + * The notification description. + * + * @var string + */ + public static string $description = 'Notification sent to user when they request to reset password.'; + + /** + * The notification package. + * + * @var string + */ + public static string $package = 'core'; + /** * Create a new notification instance. * * @return void */ - public function __construct() + public function __construct(?VerificationCode $verificationCode) { + $this->verificationCode = $verificationCode; + $this->url = Utils::consoleUrl('auth/reset-password/' . $verificationCode->uuid, ['code' => $verificationCode->code]); } /** @@ -38,9 +78,11 @@ public function via($notifiable) public function toMail($notifiable) { return (new MailMessage()) - ->line('The introduction to the notification.') - ->action('Notification Action', url('/')) - ->line('Thank you for using our application!'); + ->subject('Your password reset link for Fleetbase') + ->greeting('Hello, ' . $notifiable->name) + ->line('Looks like you (or someone phishy) has requested to reset your password. If you did not request a password reset link, ignore this email. If you have indeed forgot your password click the button below to reset your password using the code provided below.') + ->line(new HtmlString('

Your password reset code: ' . $this->verificationCode->code . '

')) + ->action('Reset Password', $this->url); } /** @@ -51,6 +93,7 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ + 'code' => $this->verificationCode->code ]; } } diff --git a/src/Notifications/PasswordResetRequest.php b/src/Notifications/PasswordResetRequest.php deleted file mode 100644 index 1518863..0000000 --- a/src/Notifications/PasswordResetRequest.php +++ /dev/null @@ -1,56 +0,0 @@ -line('The introduction to the notification.') - ->action('Notification Action', url('/')) - ->line('Thank you for using our application!'); - } - - /** - * Get the array representation of the notification. - * - * @return array - */ - public function toArray($notifiable) - { - return [ - ]; - } -} diff --git a/src/Notifications/UserAcceptedCompanyInvite.php b/src/Notifications/UserAcceptedCompanyInvite.php index 63bde03..a19835b 100644 --- a/src/Notifications/UserAcceptedCompanyInvite.php +++ b/src/Notifications/UserAcceptedCompanyInvite.php @@ -2,27 +2,75 @@ namespace Fleetbase\Notifications; +use Fleetbase\Models\Company; +use Fleetbase\Models\User; +use Fleetbase\Support\Utils; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; +/** + * Class UserAcceptedCompanyInvite + * + * Notification for when a user accepts an invitation to a company. + */ class UserAcceptedCompanyInvite extends Notification implements ShouldQueue { use Queueable; + /** + * The company that the user has joined. + * + * @var \Fleetbase\Models\Company + */ + public Company $company; + + /** + * The user who has accepted the invite. + * + * @var \Fleetbase\Models\User + */ + public User $user; + + /** + * The notification name. + * + * @var string + */ + public static string $name = 'User Accepted Company Invite'; + + /** + * The notification description. + * + * @var string + */ + public static string $description = 'Notification sent when a user has accepted a company invite.'; + + /** + * The notification package. + * + * @var string + */ + public static string $package = 'core'; + /** * Create a new notification instance. * - * @return void + * @param \Fleetbase\Models\Company $company The company model instance. + * @param \Fleetbase\Models\User $user The user model instance. */ - public function __construct() + public function __construct(Company $company, User $user) { + $this->company = $company; + $this->user = $user; } /** * Get the notification's delivery channels. * + * @param mixed $notifiable The notifiable entity. + * * @return array */ public function via($notifiable) @@ -33,24 +81,33 @@ public function via($notifiable) /** * Get the mail representation of the notification. * + * @param mixed $notifiable The notifiable entity. + * * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { return (new MailMessage()) - ->line('The introduction to the notification.') - ->action('Notification Action', url('/')) - ->line('Thank you for using our application!'); + ->subject($this->user->name . ' has joined ' . $this->company->name . ' on Fleetbase!') + ->greeting('Hello, Team!') + ->line($this->user->name . ' has accepted the invitation and has joined ' . $this->company->name . ' on Fleetbase.') + ->line('Please welcome them to the team.') + ->action('View Team Members', Utils::consoleUrl('iam/users')) + ->line('Thank you for using Fleetbase!'); } /** * Get the array representation of the notification. * + * @param mixed $notifiable The notifiable entity. + * * @return array */ public function toArray($notifiable) { return [ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, ]; } } diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index 7cf119f..deb3dc3 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -11,21 +11,72 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Carbon; +/** + * Class UserCreated + * + * @package Fleetbase\Notifications + * + * Notification for when a new user is created. + */ class UserCreated extends Notification implements ShouldQueue { use Queueable; + /** + * The user that has been created. + * + * @var \Fleetbase\Models\User|null + */ public ?User $user; + + /** + * The company the user belongs to. + * + * @var \Fleetbase\Models\Company|null + */ public ?Company $company; + + /** + * The time the notification was sent. + * + * @var string|null + */ public ?string $sentAt; + + /** + * The ID of the notification. + * + * @var string|null + */ public ?string $notificationId; + + /** + * The notification name. + * + * @var string + */ public static string $name = 'User Created'; - public static string $description = 'Notify when a new user has been created.'; + + /** + * The notification description. + * + * @var string + */ + public static string $description = 'Notification when a new user has been added to your organization.'; + + /** + * The notification package. + * + * @var string + */ public static string $package = 'core'; /** * Create a new notification instance. * + * @param \Fleetbase\Models\User $user + * @param \Fleetbase\Models\Company $company + * * @return void */ public function __construct(User $user, Company $company) @@ -54,12 +105,11 @@ public function via($notifiable) public function toMail($notifiable) { return (new MailMessage()) - ->subject('🥳 New Fleetbase Signup!') - ->line('View user details below.') + ->subject('New User Added to Your Organization') + ->line('A new user has been added to your organization.') ->line('Name: ' . $this->user->name) ->line('Email: ' . $this->user->email) - ->line('Phone: ' . $this->user->phone) - ->line('Company: ' . $this->company->name); + ->line('Phone: ' . $this->user->phone); } /** @@ -84,8 +134,8 @@ public function toArray($notifiable) return [ 'notification_id' => $this->notificationId, 'sent_at' => $this->sentAt, - 'subject' => '🥳 New Fleetbase Signup!', - 'message' => 'New user ' . $this->user->name . ' added to organization ' . $this->company->name, + 'subject' => 'New User Added to Your Organization', + 'message' => 'A new user (' . $this->user->name . ') has been added to your organization (' . $this->company->name . ').', 'id' => $this->user->uuid, 'email' => $this->user->email, 'phone' => $this->user->phone, diff --git a/src/Notifications/UserForgotPassword.php b/src/Notifications/UserForgotPassword.php index 200ef17..03a5dbf 100644 --- a/src/Notifications/UserForgotPassword.php +++ b/src/Notifications/UserForgotPassword.php @@ -14,7 +14,18 @@ class UserForgotPassword extends Notification implements ShouldQueue { use Queueable; + /** + * Instance of the verification code for the password reset. + * + * @var \Fleetbase\Models\VerificationCode|null + */ public ?VerificationCode $verificationCode; + + /** + * The URL where the user can reset their password. + * + * @var string + */ public string $url; /** @@ -61,6 +72,7 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ + 'code' => $this->verificationCode->code ]; } } diff --git a/src/Notifications/UserInvited.php b/src/Notifications/UserInvited.php index a299de9..a872609 100644 --- a/src/Notifications/UserInvited.php +++ b/src/Notifications/UserInvited.php @@ -12,13 +12,41 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\HtmlString; +/** + * Class UserInvited + * + * Notification for when a user is invited to a company. + */ class UserInvited extends Notification implements ShouldQueue { use Queueable; + /** + * The invite related to this notification. + * + * @var \Fleetbase\Models\Invite + */ public Invite $invite; + + /** + * The company related to this notification. + * + * @var \Fleetbase\Models\Company + */ public Company $company; + + /** + * The user who sent the invitation. + * + * @var \Fleetbase\Models\User + */ public User $sender; + + /** + * The URL for accepting the invite. + * + * @var string + */ public string $url; /** @@ -67,6 +95,8 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ + 'invite_id' => $this->invite->uuid, + 'subject_id' => Utils::or($this->invite, ['uuid', 'public_id', 'id']) ]; } } diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 9187265..168f3f2 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -348,7 +348,10 @@ function ($ns) use ($className) { private function registerNotifications() { - NotificationRegistry::register(\Fleetbase\Notifications\UserCreated::class); + NotificationRegistry::register([ + \Fleetbase\Notifications\UserCreated::class, + \Fleetbase\Notifications\UserAcceptedCompanyInvite::class + ]); } /** diff --git a/src/Support/NotificationRegistry.php b/src/Support/NotificationRegistry.php index 29d5d87..e1f3382 100644 --- a/src/Support/NotificationRegistry.php +++ b/src/Support/NotificationRegistry.php @@ -35,13 +35,21 @@ class NotificationRegistry * Register a notification. * * @param string|array $notificationClass The class or an array of classes to register. + * @param sarray $notificationClass The class or an array of classes to register. + * @throws \Exception * @return void */ - public static function register($notificationClass): void + public static function register($notificationClass, ?array $options = []): void { if (is_array($notificationClass)) { foreach ($notificationClass as $notificationClassElement) { - static::register($notificationClassElement); + if (is_array($notificationClassElement) && count($notificationClassElement) === 2) { + static::register($notificationClassElement[0], $notificationClassElement[1]); + } else if (is_string($notificationClassElement)) { + static::register($notificationClassElement); + } else { + throw new \Exception('Attempted to register invalid notification.'); + } } return; @@ -49,11 +57,11 @@ public static function register($notificationClass): void static::$notifications[] = [ 'definition' => $notificationClass, - 'name' => static::getNotificationClassProperty($notificationClass, 'name'), - 'description' => static::getNotificationClassProperty($notificationClass, 'description'), - 'package' => static::getNotificationClassProperty($notificationClass, 'package'), + 'name' => static::getNotificationClassProperty($notificationClass, 'name', data_get($options, 'name', null)), + 'description' => static::getNotificationClassProperty($notificationClass, 'description', data_get($options, 'description', null)), + 'package' => static::getNotificationClassProperty($notificationClass, 'package', data_get($options, 'package', null)), 'params' => static::getNotificationClassParameters($notificationClass), - 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions', []), + 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions', data_get($options, 'notificationOptions', [])), ]; } @@ -194,6 +202,23 @@ public static function getNotifiables(): array return static::getNotifiablesForCompany($companySessionId); } + /** + * Finda a notification registration by it's class. + * + * @param string $notificationClass + * @return array|null + */ + public static function findNotificationRegistrationByDefinition(string $notificationClass): ?array + { + foreach (static::$notifications as $notificationRegistration) { + if ($notificationRegistration['definition'] === $notificationClass) { + return $notificationRegistration; + } + } + + return null; + } + /** * Notify one or multiple notifiables using a specific notification class. * @@ -203,7 +228,7 @@ public static function getNotifiables(): array * @return void * @throws \Exception */ - public static function notify($notificationClass, ...$params): void + public static function notify($notificationClass, ...$params): void { // if the class doesn't exist return false if (!class_exists($notificationClass)) { @@ -212,7 +237,12 @@ public static function notify($notificationClass, ...$params): void // resolve settings for notification $notificationSettings = Setting::lookup('notification_settings'); - $notificationSettingsKey = Str::camel(str_replace('\\', '', $notificationClass)); + + // Get the notification class definition + $definition = static::findNotificationRegistrationByDefinition($notificationClass); + + // iterate the properties to find the notifications key starting with the class + $notificationSettingsKey = Str::camel(str_replace('\\', '', $notificationClass)) . '__' . Str::camel($definition['name']); // get the notification settings for this $notificationClass $settings = data_get($notificationSettings, $notificationSettingsKey, []); @@ -247,6 +277,61 @@ public static function notify($notificationClass, ...$params): void } } + /** + * Notify one or multiple notifiables using a specific notification class. + * + * @param string $notificationClass The class name of the notification to use. + * @param string $notificationName The name defined for the notification class. + * @param mixed ...$params Additional parameters for the notification class. + * + * @return void + * @throws \Exception + */ + public static function notifyUsingDefinitionName($notificationClass, $notificationName, ...$params): void + { + // if the class doesn't exist return false + if (!class_exists($notificationClass)) { + return; + } + + // resolve settings for notification + $notificationSettings = Setting::lookup('notification_settings'); + + // iterate the properties to find the notifications key starting with the class + $notificationSettingsKey = Str::camel(str_replace('\\', '', $notificationClass)) . '__' . Str::camel($notificationName); + + // get the notification settings for this $notificationClass + $settings = data_get($notificationSettings, $notificationSettingsKey, []); + + // if we have the settings resolve the notifiables + if ($settings) { + $notifiables = data_get($settings, 'notifiables', []); + + if (is_array($notifiables)) { + foreach ($notifiables as $notifiable) { + $notifiableModel = static::resolveNotifiable($notifiable); + + // if has multiple notifiables + if (isset($notifiableModel->containsMultipleNotifiables) && is_string($notifiableModel->containsMultipleNotifiables)) { + $notifiablesRelationship = $notifiableModel->containsMultipleNotifiables; + $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); + + // notifiy each + foreach ($multipleNotifiables as $singleNotifiable) { + $singleNotifiable->notify(new $notificationClass(...$params)); + } + + // continue + continue; + } + + if ($notifiableModel) { + $notifiableModel->notify(new $notificationClass(...$params)); + } + } + } + } + } /** * Resolve a notifiable object to an Eloquent model. diff --git a/src/routes.php b/src/routes.php index 32f93a2..fca5a05 100644 --- a/src/routes.php +++ b/src/routes.php @@ -137,6 +137,7 @@ function ($router, $controller) { $router->patch('deactivate/{id}', $controller('deactivate')); $router->patch('activate/{id}', $controller('activate')); $router->delete('remove-from-company/{id}', $controller('removeFromCompany')); + $router->delete('bulk-delete', $controller('bulkDelete')); $router->post('resend-invite', $controller('resendInvitation')); $router->post('set-password', $controller('setCurrentUserPassword')); } From 79e6a2edea394548118504b6b790e52486b43407 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 30 Oct 2023 18:36:53 +0800 Subject: [PATCH 8/9] bump version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3c34281..bf3236d 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/core-api", - "version": "1.3.1", + "version": "1.3.2", "description": "Core Framework and Resources for Fleetbase API", "keywords": [ "fleetbase", From 472146409e7e3e585325ff657c5c284f12b632f3 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 30 Oct 2023 18:39:01 +0800 Subject: [PATCH 9/9] ran php-cs fix --- .../Internal/v1/AuthController.php | 4 +- .../Internal/v1/NotificationController.php | 48 +++++----- src/Models/Group.php | 6 +- src/Models/Notification.php | 9 +- src/Notifications/PasswordReset.php | 12 +-- .../UserAcceptedCompanyInvite.php | 26 ++---- src/Notifications/UserCreated.php | 49 +++------- src/Notifications/UserForgotPassword.php | 6 +- src/Notifications/UserInvited.php | 14 +-- src/Observers/NotificationObserver.php | 2 - src/Providers/CoreServiceProvider.php | 4 +- src/Support/NotificationRegistry.php | 90 +++++++++---------- src/Webhook/CallWebhookJob.php | 34 +++---- src/Webhook/WebhookCall.php | 20 ++--- 14 files changed, 130 insertions(+), 194 deletions(-) diff --git a/src/Http/Controllers/Internal/v1/AuthController.php b/src/Http/Controllers/Internal/v1/AuthController.php index 5dcb259..15508a1 100644 --- a/src/Http/Controllers/Internal/v1/AuthController.php +++ b/src/Http/Controllers/Internal/v1/AuthController.php @@ -276,8 +276,8 @@ public function createPasswordReset(UserForgotPasswordRequest $request) public function resetPassword(ResetPasswordRequest $request) { $verificationCode = VerificationCode::where('code', $request->input('code'))->with(['subject'])->first(); - $link = $request->input('link'); - $password = $request->input('password'); + $link = $request->input('link'); + $password = $request->input('password'); // If link isn't valid if ($verificationCode->uuid !== $link) { diff --git a/src/Http/Controllers/Internal/v1/NotificationController.php b/src/Http/Controllers/Internal/v1/NotificationController.php index b7f7b60..50ebd8a 100644 --- a/src/Http/Controllers/Internal/v1/NotificationController.php +++ b/src/Http/Controllers/Internal/v1/NotificationController.php @@ -23,14 +23,15 @@ class NotificationController extends FleetbaseController /** * Receives an array of ID's for notifications which should be marked as read. * - * @param \Illuminate\Http\Request $request The HTTP request object. - * @return \Illuminate\Http\Response The HTTP response. + * @param \Illuminate\Http\Request $request the HTTP request object + * + * @return \Illuminate\Http\Response the HTTP response */ public function markAsRead(Request $request) { $notifications = $request->input('notifications'); - $total = count($notifications); - $read = []; + $total = count($notifications); + $read = []; foreach ($notifications as $id) { $notification = Notification::where('id', $id)->first(); @@ -41,17 +42,17 @@ public function markAsRead(Request $request) } return response()->json([ - 'status' => 'ok', - 'message' => 'Notifications marked as read', + 'status' => 'ok', + 'message' => 'Notifications marked as read', 'marked_as_read' => count($read), - 'total' => $total + 'total' => $total, ]); } /** * Receives an array of ID's for notifications which should be marked as read. * - * @return \Illuminate\Http\Response The HTTP response. + * @return \Illuminate\Http\Response the HTTP response */ public function markAllAsRead() { @@ -62,7 +63,7 @@ public function markAllAsRead() } return response()->json([ - 'status' => 'ok', + 'status' => 'ok', 'message' => 'All notifications marked as read', ]); } @@ -70,8 +71,9 @@ public function markAllAsRead() /** * Deletes a single notification. * - * @param int $notificationId The ID of the notification to delete. - * @return \Illuminate\Http\Response The HTTP response. + * @param int $notificationId the ID of the notification to delete + * + * @return \Illuminate\Http\Response the HTTP response */ public function deleteNotification($notificationId) { @@ -79,6 +81,7 @@ public function deleteNotification($notificationId) if ($notification) { $notification->deleteNotification(); + return response()->json(['message' => 'Notification deleted successfully'], 200); } else { return response()->json(['error' => 'Notification not found'], 404); @@ -88,8 +91,9 @@ public function deleteNotification($notificationId) /** * Deletes all notifications for the authenticated user. * - * @param \Illuminate\Http\Request $request The HTTP request object. - * @return \Illuminate\Http\Response The HTTP response. + * @param \Illuminate\Http\Request $request the HTTP request object + * + * @return \Illuminate\Http\Response the HTTP response */ public function bulkDelete(Request $request) { @@ -102,7 +106,7 @@ public function bulkDelete(Request $request) } return response()->json([ - 'status' => 'ok', + 'status' => 'ok', 'message' => 'Selected notifications deleted successfully', ]); } @@ -125,12 +129,11 @@ public function notifiables() /** * Save user notification settings. * + * @param Request $request the HTTP request object containing the notification settings data * - * @param Request $request The HTTP request object containing the notification settings data. - * - * @throws \Exception If the provided notification settings data is not an array. + * @return \Illuminate\Http\JsonResponse a JSON response * - * @return \Illuminate\Http\JsonResponse A JSON response. + * @throws \Exception if the provided notification settings data is not an array */ public function saveSettings(Request $request) { @@ -141,7 +144,7 @@ public function saveSettings(Request $request) Setting::configure('notification_settings', $notificationSettings); return response()->json([ - 'status' => 'ok', + 'status' => 'ok', 'message' => 'Notification settings succesfully saved.', ]); } @@ -149,16 +152,15 @@ public function saveSettings(Request $request) /** * Retrieve and return the notification settings for the user. * - * - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\JsonResponse */ public function getSettings() { $notificationSettings = Setting::lookup('notification_settings'); return response()->json([ - 'status' => 'ok', - 'message' => 'Notification settings successfully fetched.', + 'status' => 'ok', + 'message' => 'Notification settings successfully fetched.', 'notificationSettings' => $notificationSettings, ]); } diff --git a/src/Models/Group.php b/src/Models/Group.php index e17144b..7a01778 100644 --- a/src/Models/Group.php +++ b/src/Models/Group.php @@ -13,7 +13,7 @@ use Spatie\Sluggable\SlugOptions; /** - * @property \Illuminate\Database\Eloquent\Collection $users + * @property \Illuminate\Database\Eloquent\Collection $users */ class Group extends Model { @@ -63,8 +63,6 @@ class Group extends Model /** * The relationship of the multiple notifiables. - * - * @var string */ public string $containsMultipleNotifiables = 'users'; @@ -90,8 +88,6 @@ public function users() /** * An array of each group members email to send notification emails to. - * - * @return \Illuminate\Support\Collection */ public function routeNotificationForMail(): \Illuminate\Support\Collection { diff --git a/src/Models/Notification.php b/src/Models/Notification.php index a76b93d..9f09ed0 100644 --- a/src/Models/Notification.php +++ b/src/Models/Notification.php @@ -9,7 +9,9 @@ class Notification extends DatabaseNotification { - use HasApiModelBehavior, Searchable, Filterable; + use HasApiModelBehavior; + use Searchable; + use Filterable; /** * The attributes that are mass assignable. @@ -28,8 +30,7 @@ class Notification extends DatabaseNotification /** * Marks the notification as read. * - * @param boolean $save - * @return \Fleetbase\Models\Notification + * @param bool $save */ public function markAsRead($save = true): Notification { @@ -41,6 +42,7 @@ public function markAsRead($save = true): Notification return $this; } + /** * Delete the notification. * @@ -50,5 +52,4 @@ public function deleteNotification() { $this->delete(); } - } diff --git a/src/Notifications/PasswordReset.php b/src/Notifications/PasswordReset.php index 69f99cb..72ae984 100644 --- a/src/Notifications/PasswordReset.php +++ b/src/Notifications/PasswordReset.php @@ -16,36 +16,26 @@ class PasswordReset extends Notification implements ShouldQueue /** * Instance of the verification code for the password reset. - * - * @var \Fleetbase\Models\VerificationCode|null */ public ?VerificationCode $verificationCode; /** * The URL where the user can reset their password. - * - * @var string */ public string $url; /** * The notification name. - * - * @var string */ public static string $name = 'Password Reset'; /** * The notification description. - * - * @var string */ public static string $description = 'Notification sent to user when they request to reset password.'; /** * The notification package. - * - * @var string */ public static string $package = 'core'; @@ -93,7 +83,7 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ - 'code' => $this->verificationCode->code + 'code' => $this->verificationCode->code, ]; } } diff --git a/src/Notifications/UserAcceptedCompanyInvite.php b/src/Notifications/UserAcceptedCompanyInvite.php index a19835b..fdaf7cb 100644 --- a/src/Notifications/UserAcceptedCompanyInvite.php +++ b/src/Notifications/UserAcceptedCompanyInvite.php @@ -11,7 +11,7 @@ use Illuminate\Notifications\Notification; /** - * Class UserAcceptedCompanyInvite + * Class UserAcceptedCompanyInvite. * * Notification for when a user accepts an invitation to a company. */ @@ -21,55 +21,45 @@ class UserAcceptedCompanyInvite extends Notification implements ShouldQueue /** * The company that the user has joined. - * - * @var \Fleetbase\Models\Company */ public Company $company; /** * The user who has accepted the invite. - * - * @var \Fleetbase\Models\User */ public User $user; /** * The notification name. - * - * @var string */ public static string $name = 'User Accepted Company Invite'; /** * The notification description. - * - * @var string */ public static string $description = 'Notification sent when a user has accepted a company invite.'; /** * The notification package. - * - * @var string */ public static string $package = 'core'; /** * Create a new notification instance. * - * @param \Fleetbase\Models\Company $company The company model instance. - * @param \Fleetbase\Models\User $user The user model instance. + * @param \Fleetbase\Models\Company $company the company model instance + * @param \Fleetbase\Models\User $user the user model instance */ public function __construct(Company $company, User $user) { $this->company = $company; - $this->user = $user; + $this->user = $user; } /** * Get the notification's delivery channels. * - * @param mixed $notifiable The notifiable entity. + * @param mixed $notifiable the notifiable entity * * @return array */ @@ -81,7 +71,7 @@ public function via($notifiable) /** * Get the mail representation of the notification. * - * @param mixed $notifiable The notifiable entity. + * @param mixed $notifiable the notifiable entity * * @return \Illuminate\Notifications\Messages\MailMessage */ @@ -99,7 +89,7 @@ public function toMail($notifiable) /** * Get the array representation of the notification. * - * @param mixed $notifiable The notifiable entity. + * @param mixed $notifiable the notifiable entity * * @return array */ @@ -107,7 +97,7 @@ public function toArray($notifiable) { return [ 'company_id' => $this->company->id, - 'user_id' => $this->user->id, + 'user_id' => $this->user->id, ]; } } diff --git a/src/Notifications/UserCreated.php b/src/Notifications/UserCreated.php index deb3dc3..5ccec84 100644 --- a/src/Notifications/UserCreated.php +++ b/src/Notifications/UserCreated.php @@ -6,17 +6,13 @@ use Fleetbase\Models\User; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\BroadcastMessage; +use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; use Illuminate\Support\Carbon; /** - * Class UserCreated - * - * @package Fleetbase\Notifications - * - * Notification for when a new user is created. + * Class UserCreated. */ class UserCreated extends Notification implements ShouldQueue { @@ -24,66 +20,49 @@ class UserCreated extends Notification implements ShouldQueue /** * The user that has been created. - * - * @var \Fleetbase\Models\User|null */ public ?User $user; /** * The company the user belongs to. - * - * @var \Fleetbase\Models\Company|null */ public ?Company $company; /** * The time the notification was sent. - * - * @var string|null */ public ?string $sentAt; /** * The ID of the notification. - * - * @var string|null */ public ?string $notificationId; /** * The notification name. - * - * @var string */ public static string $name = 'User Created'; /** * The notification description. - * - * @var string */ public static string $description = 'Notification when a new user has been added to your organization.'; /** * The notification package. - * - * @var string */ public static string $package = 'core'; /** * Create a new notification instance. * - * @param \Fleetbase\Models\User $user - * @param \Fleetbase\Models\Company $company - * * @return void */ public function __construct(User $user, Company $company) { - $this->user = $user; - $this->company = $company; - $this->sentAt = Carbon::now()->toDateTimeString(); + $this->user = $user; + $this->company = $company; + $this->sentAt = Carbon::now()->toDateTimeString(); $this->notificationId = uniqid('notification_'); } @@ -115,7 +94,6 @@ public function toMail($notifiable) /** * Get the broadcastable representation of the notification. * - * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\BroadcastMessage */ public function toBroadcast($notifiable) @@ -126,21 +104,20 @@ public function toBroadcast($notifiable) /** * Get the array representation of the notification. * - * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ 'notification_id' => $this->notificationId, - 'sent_at' => $this->sentAt, - 'subject' => 'New User Added to Your Organization', - 'message' => 'A new user (' . $this->user->name . ') has been added to your organization (' . $this->company->name . ').', - 'id' => $this->user->uuid, - 'email' => $this->user->email, - 'phone' => $this->user->phone, - 'companyId' => $this->company->uuid, - 'company' => $this->company->name, + 'sent_at' => $this->sentAt, + 'subject' => 'New User Added to Your Organization', + 'message' => 'A new user (' . $this->user->name . ') has been added to your organization (' . $this->company->name . ').', + 'id' => $this->user->uuid, + 'email' => $this->user->email, + 'phone' => $this->user->phone, + 'companyId' => $this->company->uuid, + 'company' => $this->company->name, ]; } } diff --git a/src/Notifications/UserForgotPassword.php b/src/Notifications/UserForgotPassword.php index 03a5dbf..c7d3b7e 100644 --- a/src/Notifications/UserForgotPassword.php +++ b/src/Notifications/UserForgotPassword.php @@ -16,15 +16,11 @@ class UserForgotPassword extends Notification implements ShouldQueue /** * Instance of the verification code for the password reset. - * - * @var \Fleetbase\Models\VerificationCode|null */ public ?VerificationCode $verificationCode; /** * The URL where the user can reset their password. - * - * @var string */ public string $url; @@ -72,7 +68,7 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ - 'code' => $this->verificationCode->code + 'code' => $this->verificationCode->code, ]; } } diff --git a/src/Notifications/UserInvited.php b/src/Notifications/UserInvited.php index a872609..fb7dbfa 100644 --- a/src/Notifications/UserInvited.php +++ b/src/Notifications/UserInvited.php @@ -13,7 +13,7 @@ use Illuminate\Support\HtmlString; /** - * Class UserInvited + * Class UserInvited. * * Notification for when a user is invited to a company. */ @@ -23,29 +23,21 @@ class UserInvited extends Notification implements ShouldQueue /** * The invite related to this notification. - * - * @var \Fleetbase\Models\Invite */ public Invite $invite; /** * The company related to this notification. - * - * @var \Fleetbase\Models\Company */ public Company $company; /** * The user who sent the invitation. - * - * @var \Fleetbase\Models\User */ public User $sender; /** * The URL for accepting the invite. - * - * @var string */ public string $url; @@ -95,8 +87,8 @@ public function toMail($notifiable) public function toArray($notifiable) { return [ - 'invite_id' => $this->invite->uuid, - 'subject_id' => Utils::or($this->invite, ['uuid', 'public_id', 'id']) + 'invite_id' => $this->invite->uuid, + 'subject_id' => Utils::or($this->invite, ['uuid', 'public_id', 'id']), ]; } } diff --git a/src/Observers/NotificationObserver.php b/src/Observers/NotificationObserver.php index e5429d1..d215ccb 100644 --- a/src/Observers/NotificationObserver.php +++ b/src/Observers/NotificationObserver.php @@ -2,8 +2,6 @@ namespace Fleetbase\Observers; -use Fleetbase\Models\Notification; - class NotificationObserver { } diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 168f3f2..d26dfd6 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -3,8 +3,8 @@ namespace Fleetbase\Providers; use Fleetbase\Models\Setting; -use Fleetbase\Support\NotificationRegistry; use Fleetbase\Support\Expansion; +use Fleetbase\Support\NotificationRegistry; use Fleetbase\Support\Utils; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Http\Resources\Json\JsonResource; @@ -350,7 +350,7 @@ private function registerNotifications() { NotificationRegistry::register([ \Fleetbase\Notifications\UserCreated::class, - \Fleetbase\Notifications\UserAcceptedCompanyInvite::class + \Fleetbase\Notifications\UserAcceptedCompanyInvite::class, ]); } diff --git a/src/Support/NotificationRegistry.php b/src/Support/NotificationRegistry.php index e1f3382..29e2388 100644 --- a/src/Support/NotificationRegistry.php +++ b/src/Support/NotificationRegistry.php @@ -3,7 +3,6 @@ namespace Fleetbase\Support; use Fleetbase\Models\Setting; -use Fleetbase\Support\Utils; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; @@ -34,10 +33,10 @@ class NotificationRegistry /** * Register a notification. * - * @param string|array $notificationClass The class or an array of classes to register. - * @param sarray $notificationClass The class or an array of classes to register. + * @param string|array $notificationClass the class or an array of classes to register + * @param sarray $notificationClass the class or an array of classes to register + * * @throws \Exception - * @return void */ public static function register($notificationClass, ?array $options = []): void { @@ -45,7 +44,7 @@ public static function register($notificationClass, ?array $options = []): void foreach ($notificationClass as $notificationClassElement) { if (is_array($notificationClassElement) && count($notificationClassElement) === 2) { static::register($notificationClassElement[0], $notificationClassElement[1]); - } else if (is_string($notificationClassElement)) { + } elseif (is_string($notificationClassElement)) { static::register($notificationClassElement); } else { throw new \Exception('Attempted to register invalid notification.'); @@ -56,20 +55,19 @@ public static function register($notificationClass, ?array $options = []): void } static::$notifications[] = [ - 'definition' => $notificationClass, - 'name' => static::getNotificationClassProperty($notificationClass, 'name', data_get($options, 'name', null)), + 'definition' => $notificationClass, + 'name' => static::getNotificationClassProperty($notificationClass, 'name', data_get($options, 'name', null)), 'description' => static::getNotificationClassProperty($notificationClass, 'description', data_get($options, 'description', null)), - 'package' => static::getNotificationClassProperty($notificationClass, 'package', data_get($options, 'package', null)), - 'params' => static::getNotificationClassParameters($notificationClass), - 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions', data_get($options, 'notificationOptions', [])), + 'package' => static::getNotificationClassProperty($notificationClass, 'package', data_get($options, 'package', null)), + 'params' => static::getNotificationClassParameters($notificationClass), + 'options' => static::getNotificationClassProperty($notificationClass, 'notificationOptions', data_get($options, 'notificationOptions', [])), ]; } /** * Register a notifiable. * - * @param string|array $notifiableClass The class of the notifiable. - * @return void + * @param string|array $notifiableClass the class of the notifiable */ public static function registerNotifiable($notifiableClass): void { @@ -87,10 +85,11 @@ public static function registerNotifiable($notifiableClass): void /** * Get a property of a notification class. * - * @param string $notificationClass The class name. - * @param string $property The name of the property to retrieve. - * @param mixed $defaultValue The default value if the property is not found. - * @return mixed|null The value of the property or null if not found. + * @param string $notificationClass the class name + * @param string $property the name of the property to retrieve + * @param mixed $defaultValue the default value if the property is not found + * + * @return mixed|null the value of the property or null if not found */ private static function getNotificationClassProperty(string $notificationClass, string $property, $defaultValue = null) { @@ -99,15 +98,16 @@ private static function getNotificationClassProperty(string $notificationClass, } $properties = get_class_vars($notificationClass); + return data_get($properties, $property, $defaultValue); } /** * Get the parameters required by a specific notification class constructor. * - * @param string $notificationClass The class name of the notification. + * @param string $notificationClass the class name of the notification * - * @return array An array of associative arrays, each containing details about a parameter required by the constructor. + * @return array an array of associative arrays, each containing details about a parameter required by the constructor */ private function getNotificationClassParameters(string $notificationClass): array { @@ -136,9 +136,9 @@ private function getNotificationClassParameters(string $notificationClass): arra $isOptional = $param->isOptional(); $requiredParams[] = [ - 'name' => $name, - 'type' => $type ? $type->getName() : 'mixed', // If type is null, set it as 'mixed' - 'optional' => $isOptional + 'name' => $name, + 'type' => $type ? $type->getName() : 'mixed', // If type is null, set it as 'mixed' + 'optional' => $isOptional, ]; } @@ -147,8 +147,6 @@ private function getNotificationClassParameters(string $notificationClass): arra /** * Get all notificables for a company. - * - * @return array */ public static function getNotifiablesForCompany(string $companyId): array { @@ -164,24 +162,24 @@ public static function getNotifiablesForCompany(string $companyId): array // iterate through each notifiable types and get records foreach (static::$notifiables as $notifiableClass) { $notifiableModel = app($notifiableClass); - $type = class_basename($notifiableClass); + $type = class_basename($notifiableClass); if ($notifiableModel && $notifiableModel instanceof \Illuminate\Database\Eloquent\Model) { - $table = $notifiableModel->getTable(); - $modelClass = get_class($notifiableModel); + $table = $notifiableModel->getTable(); + $modelClass = get_class($notifiableModel); $hasCompanyColumn = Schema::hasColumn($table, 'company_uuid'); if ($hasCompanyColumn) { $records = $notifiableModel->where('company_uuid', $companySessionId)->get(); foreach ($records as $record) { - $recordId = Utils::or($record, ['uuid', 'id']); + $recordId = Utils::or($record, ['uuid', 'id']); $notifiables[] = [ - 'label' => Str::title($type) . ': ' . Utils::or($record, ['name', 'public_id']), - 'key' => $recordId, + 'label' => Str::title($type) . ': ' . Utils::or($record, ['name', 'public_id']), + 'key' => $recordId, 'primaryKey' => $notifiableModel->getKeyName(), 'definition' => $modelClass, - 'value' => Str::slug(str_replace('\\', '-', $modelClass)) . ':' . $recordId, + 'value' => Str::slug(str_replace('\\', '-', $modelClass)) . ':' . $recordId, ]; } } @@ -193,20 +191,16 @@ public static function getNotifiablesForCompany(string $companyId): array /** * Gets all notifiables for the current company session. - * - * @return array */ public static function getNotifiables(): array { $companySessionId = session('company'); + return static::getNotifiablesForCompany($companySessionId); } /** * Finda a notification registration by it's class. - * - * @param string $notificationClass - * @return array|null */ public static function findNotificationRegistrationByDefinition(string $notificationClass): ?array { @@ -222,10 +216,9 @@ public static function findNotificationRegistrationByDefinition(string $notifica /** * Notify one or multiple notifiables using a specific notification class. * - * @param string $notificationClass The class name of the notification to use. - * @param mixed ...$params Additional parameters for the notification class. + * @param string $notificationClass the class name of the notification to use + * @param mixed ...$params Additional parameters for the notification class. * - * @return void * @throws \Exception */ public static function notify($notificationClass, ...$params): void @@ -255,10 +248,10 @@ public static function notify($notificationClass, ...$params): void foreach ($notifiables as $notifiable) { $notifiableModel = static::resolveNotifiable($notifiable); - // if has multiple notifiables + // if has multiple notifiables if (isset($notifiableModel->containsMultipleNotifiables) && is_string($notifiableModel->containsMultipleNotifiables)) { $notifiablesRelationship = $notifiableModel->containsMultipleNotifiables; - $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); + $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); // notifiy each foreach ($multipleNotifiables as $singleNotifiable) { @@ -280,11 +273,10 @@ public static function notify($notificationClass, ...$params): void /** * Notify one or multiple notifiables using a specific notification class. * - * @param string $notificationClass The class name of the notification to use. - * @param string $notificationName The name defined for the notification class. - * @param mixed ...$params Additional parameters for the notification class. + * @param string $notificationClass the class name of the notification to use + * @param string $notificationName the name defined for the notification class + * @param mixed ...$params Additional parameters for the notification class. * - * @return void * @throws \Exception */ public static function notifyUsingDefinitionName($notificationClass, $notificationName, ...$params): void @@ -311,10 +303,10 @@ public static function notifyUsingDefinitionName($notificationClass, $notificati foreach ($notifiables as $notifiable) { $notifiableModel = static::resolveNotifiable($notifiable); - // if has multiple notifiables + // if has multiple notifiables if (isset($notifiableModel->containsMultipleNotifiables) && is_string($notifiableModel->containsMultipleNotifiables)) { $notifiablesRelationship = $notifiableModel->containsMultipleNotifiables; - $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); + $multipleNotifiables = data_get($notifiableModel, $notifiablesRelationship, []); // notifiy each foreach ($multipleNotifiables as $singleNotifiable) { @@ -336,15 +328,15 @@ public static function notifyUsingDefinitionName($notificationClass, $notificati /** * Resolve a notifiable object to an Eloquent model. * - * @param array $notifiableObject An associative array containing the definition and primary key to resolve the notifiable object. + * @param array $notifiableObject an associative array containing the definition and primary key to resolve the notifiable object * - * @return \Illuminate\Database\Eloquent\Model|null The Eloquent model or null if it cannot be resolved. + * @return \Illuminate\Database\Eloquent\Model|null the Eloquent model or null if it cannot be resolved */ protected static function resolveNotifiable(array $notifiableObject): ?\Illuminate\Database\Eloquent\Model { $definition = data_get($notifiableObject, 'definition'); $primaryKey = data_get($notifiableObject, 'primaryKey'); - $key = data_get($notifiableObject, 'key'); + $key = data_get($notifiableObject, 'key'); // resolve the notifiable $modelInstance = app($definition); diff --git a/src/Webhook/CallWebhookJob.php b/src/Webhook/CallWebhookJob.php index 4cc2b94..824f367 100644 --- a/src/Webhook/CallWebhookJob.php +++ b/src/Webhook/CallWebhookJob.php @@ -2,7 +2,9 @@ namespace Fleetbase\Webhook; -use Exception; +use Fleetbase\Webhook\Events\FinalWebhookCallFailedEvent; +use Fleetbase\Webhook\Events\WebhookCallFailedEvent; +use Fleetbase\Webhook\Events\WebhookCallSucceededEvent; use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ConnectException; @@ -15,13 +17,13 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; -use Fleetbase\Webhook\Events\FinalWebhookCallFailedEvent; -use Fleetbase\Webhook\Events\WebhookCallFailedEvent; -use Fleetbase\Webhook\Events\WebhookCallSucceededEvent; class CallWebhookJob implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable; + use InteractsWithQueue; + use Queueable; + use SerializesModels; public ?string $webhookUrl = null; @@ -44,7 +46,7 @@ class CallWebhookJob implements ShouldQueue public bool $throwExceptionOnFailure; /** @var string|null */ - public $queue = null; + public $queue; public array $payload = []; @@ -73,26 +75,26 @@ public function handle() $this->response = $this->createRequest($body); - if (! Str::startsWith($this->response->getStatusCode(), 2)) { - throw new Exception('Webhook call failed'); + if (!Str::startsWith($this->response->getStatusCode(), 2)) { + throw new \Exception('Webhook call failed'); } $this->dispatchEvent(WebhookCallSucceededEvent::class); return; - } catch (Exception $exception) { + } catch (\Exception $exception) { if ($exception instanceof RequestException) { - $this->response = $exception->getResponse(); - $this->errorType = get_class($exception); + $this->response = $exception->getResponse(); + $this->errorType = get_class($exception); $this->errorMessage = $exception->getMessage(); } if ($exception instanceof ConnectException) { - $this->errorType = get_class($exception); + $this->errorType = get_class($exception); $this->errorMessage = $exception->getMessage(); } - if (! $lastAttempt) { + if (!$lastAttempt) { /** @var \Fleetbase\Webhook\BackoffStrategy\BackoffStrategy $backoffStrategy */ $backoffStrategy = app($this->backoffStrategyClass); @@ -131,9 +133,9 @@ protected function createRequest(array $body): Response $client = $this->getClient(); return $client->request($this->httpVerb, $this->webhookUrl, array_merge([ - 'timeout' => $this->requestTimeout, - 'verify' => $this->verifySsl, - 'headers' => $this->headers, + 'timeout' => $this->requestTimeout, + 'verify' => $this->verifySsl, + 'headers' => $this->headers, 'on_stats' => function (TransferStats $stats) { $this->transferStats = $stats; }, diff --git a/src/Webhook/WebhookCall.php b/src/Webhook/WebhookCall.php index be56354..c41736a 100644 --- a/src/Webhook/WebhookCall.php +++ b/src/Webhook/WebhookCall.php @@ -2,14 +2,14 @@ namespace Fleetbase\Webhook; -use Illuminate\Foundation\Bus\PendingDispatch; -use Illuminate\Support\Str; use Fleetbase\Webhook\BackoffStrategy\BackoffStrategy; use Fleetbase\Webhook\Exceptions\CouldNotCallWebhook; use Fleetbase\Webhook\Exceptions\InvalidBackoffStrategy; use Fleetbase\Webhook\Exceptions\InvalidSigner; use Fleetbase\Webhook\Exceptions\InvalidWebhookJob; use Fleetbase\Webhook\Signer\Signer; +use Illuminate\Foundation\Bus\PendingDispatch; +use Illuminate\Support\Str; class WebhookCall { @@ -119,7 +119,7 @@ public function maximumTries(int $tries): self public function useBackoffStrategy(string $backoffStrategyClass): self { - if (! is_subclass_of($backoffStrategyClass, BackoffStrategy::class)) { + if (!is_subclass_of($backoffStrategyClass, BackoffStrategy::class)) { throw InvalidBackoffStrategy::doesNotExtendBackoffStrategy($backoffStrategyClass); } @@ -137,7 +137,7 @@ public function timeoutInSeconds(int $timeoutInSeconds): self public function signUsing(string $signerClass): self { - if (! is_subclass_of($signerClass, Signer::class)) { + if (!is_subclass_of($signerClass, Signer::class)) { throw InvalidSigner::doesNotImplementSigner($signerClass); } @@ -181,7 +181,7 @@ public function throwExceptionOnFailure(bool $throwExceptionOnFailure = true): s return $this; } - public function useProxy(array|string|null $proxy = null): self + public function useProxy(array|string $proxy = null): self { $this->callWebhookJob->proxy = $proxy; @@ -206,7 +206,7 @@ public function useJob(string $webhookJobClass): self { $job = app($webhookJobClass); - if (! $job instanceof CallWebhookJob) { + if (!$job instanceof CallWebhookJob) { throw InvalidWebhookJob::doesNotExtendCallWebhookJob($webhookJobClass); } @@ -233,7 +233,7 @@ public function dispatchIf($condition): PendingDispatch|null public function dispatchUnless($condition): PendingDispatch|null { - return $this->dispatchIf(! $condition); + return $this->dispatchIf(!$condition); } public function dispatchSync(): void @@ -252,12 +252,12 @@ public function dispatchSyncIf($condition): void public function dispatchSyncUnless($condition): void { - $this->dispatchSyncIf(! $condition); + $this->dispatchSyncIf(!$condition); } protected function prepareForDispatch(): void { - if (! $this->callWebhookJob->webhookUrl) { + if (!$this->callWebhookJob->webhookUrl) { throw CouldNotCallWebhook::urlNotSet(); } @@ -272,7 +272,7 @@ protected function getAllHeaders(): array { $headers = $this->headers; - if (! $this->signWebhook) { + if (!$this->signWebhook) { return $headers; }