144 lines
3.9 KiB
PHP
144 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\Notification;
|
|
use App\Models\PushSubscription;
|
|
use App\Models\SystemConfiguration;
|
|
use App\Models\User;
|
|
use App\Support\Media\MediaPresenter;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
use Minishlink\WebPush\Subscription;
|
|
use Minishlink\WebPush\WebPush;
|
|
|
|
class SendPushNotificationJob implements ShouldQueue
|
|
{
|
|
use Queueable;
|
|
|
|
public function __construct(
|
|
private readonly string $title,
|
|
private readonly string $body,
|
|
private readonly string $url = '/admin/dashboard',
|
|
private readonly array $roles = [],
|
|
private readonly ?int $userId = null,
|
|
) {}
|
|
|
|
public function handle(): void
|
|
{
|
|
$this->persistNotifications();
|
|
$this->sendWebPush();
|
|
}
|
|
|
|
private function persistNotifications(): void
|
|
{
|
|
$userIds = $this->resolveUserIds();
|
|
|
|
foreach ($userIds as $uid) {
|
|
Notification::create([
|
|
'user_id' => $uid,
|
|
'title' => $this->title,
|
|
'body' => $this->body,
|
|
'url' => $this->url,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function sendWebPush(): void
|
|
{
|
|
$subscriptions = $this->resolveSubscriptions();
|
|
|
|
if ($subscriptions->isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
$icon = $this->resolveIcon();
|
|
|
|
$webPush = new WebPush([
|
|
'VAPID' => [
|
|
'subject' => config('app.url'),
|
|
'publicKey' => config('webpush.vapid.public_key'),
|
|
'privateKey' => config('webpush.vapid.private_key'),
|
|
],
|
|
]);
|
|
|
|
$payload = json_encode([
|
|
'title' => $this->title,
|
|
'body' => $this->body,
|
|
'icon' => $icon,
|
|
'url' => $this->url,
|
|
]);
|
|
|
|
foreach ($subscriptions as $subscription) {
|
|
$sub = Subscription::create([
|
|
'endpoint' => $subscription->endpoint,
|
|
'publicKey' => $subscription->public_key,
|
|
'authToken' => $subscription->auth_token,
|
|
'contentEncoding' => 'aes128gcm',
|
|
]);
|
|
|
|
$webPush->queueNotification($sub, $payload);
|
|
}
|
|
|
|
$expiredEndpoints = [];
|
|
|
|
foreach ($webPush->flush() as $report) {
|
|
if ($report->isSubscriptionExpired()) {
|
|
$expiredEndpoints[] = $report->getEndpoint();
|
|
}
|
|
}
|
|
|
|
if (! empty($expiredEndpoints)) {
|
|
PushSubscription::whereIn('endpoint', $expiredEndpoints)->delete();
|
|
}
|
|
}
|
|
|
|
private function resolveIcon(): string
|
|
{
|
|
try {
|
|
$configuration = SystemConfiguration::instance();
|
|
$configuration->load('media');
|
|
$logo = MediaPresenter::first($configuration, 'logo');
|
|
|
|
return $logo['url'] ?? config('app.url').'/assets/logo.png';
|
|
} catch (\Throwable) {
|
|
return config('app.url').'/assets/logo.png';
|
|
}
|
|
}
|
|
|
|
private function resolveSubscriptions()
|
|
{
|
|
$query = PushSubscription::query();
|
|
|
|
if ($this->userId !== null) {
|
|
$query->where('user_id', $this->userId);
|
|
} elseif (! empty($this->roles)) {
|
|
$query->whereHas('user', function (Builder $userQuery): void {
|
|
$userQuery->whereHas('roles', fn (Builder $roleQuery) => $roleQuery->whereIn('name', $this->roles));
|
|
});
|
|
}
|
|
|
|
return $query->get();
|
|
}
|
|
|
|
/**
|
|
* @return list<int>
|
|
*/
|
|
private function resolveUserIds(): array
|
|
{
|
|
if ($this->userId !== null) {
|
|
return [$this->userId];
|
|
}
|
|
|
|
if (! empty($this->roles)) {
|
|
return User::query()
|
|
->whereHas('roles', fn (Builder $q) => $q->whereIn('name', $this->roles))
|
|
->pluck('id')
|
|
->all();
|
|
}
|
|
|
|
return User::query()->pluck('id')->all();
|
|
}
|
|
}
|