feat: add persistent mobile notifications
CI / 🧪 Tests Laravel (push) Successful in 2m7s
CI / 🐳 Build & Push Image (push) Successful in 42s

This commit is contained in:
2026-08-11 16:46:05 +02:00
parent bd1b4bba20
commit df4f523089
24 changed files with 822 additions and 75 deletions
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Notifications;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
abstract class MobileNotification extends Notification implements ShouldQueue
{
use Queueable;
public const array TYPES = [
'certification_approved',
'certification_rejected',
'engagement_reminder',
'meal_commented',
'new_follower',
'payment_failed',
];
protected const string CATEGORY_ACCOUNT = 'account';
protected const string CATEGORY_ENGAGEMENT = 'engagement';
protected const string CATEGORY_SOCIAL = 'social';
public function __construct()
{
$this->afterCommit();
}
/** @return list<string> */
public function via(object $notifiable): array
{
if ($this->category() === self::CATEGORY_ENGAGEMENT
&& $notifiable instanceof User
&& ! $notifiable->engagement_reminders_enabled) {
return [];
}
$channels = ['database'];
if ($this->shouldSendPush($notifiable)) {
$channels[] = 'expo';
}
return $channels;
}
/** @return array<string, mixed> */
public function toDatabase(object $notifiable): array
{
return [
...$this->payload($notifiable),
'category' => $this->category(),
];
}
public function databaseType(object $notifiable): string
{
return $this->payload($notifiable)['type'];
}
/** @return array<string, mixed> */
public function toExpoPush(object $notifiable): array
{
$payload = $this->payload($notifiable);
return [
'title' => $payload['title'],
'body' => $payload['body'],
'sound' => 'default',
'channelId' => 'default',
'data' => [
'type' => $payload['type'],
'url' => $payload['url'],
...($this->id ? ['notificationId' => $this->id] : []),
...($payload['data'] ?? []),
],
];
}
abstract protected function category(): string;
/** @return array{type: string, title: string, body: string, url: string, data?: array<string, mixed>} */
abstract protected function payload(object $notifiable): array;
private function shouldSendPush(object $notifiable): bool
{
if (! $notifiable instanceof User) {
return false;
}
return $this->category() !== self::CATEGORY_SOCIAL
|| $notifiable->social_notifications_enabled;
}
}