Add guest push notifications and queue alerts
This commit is contained in:
@@ -3,7 +3,11 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Console\Concerns\InteractsWithCacheLocks;
|
||||
use App\Enums\GuestNotificationAudience;
|
||||
use App\Enums\GuestNotificationType;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventMediaAsset;
|
||||
use App\Services\GuestNotificationService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Cache\Lock;
|
||||
use Illuminate\Queue\QueueManager;
|
||||
@@ -20,6 +24,11 @@ class CheckUploadQueuesCommand extends Command
|
||||
|
||||
protected $description = 'Inspect upload-related queues and flag stalled or overloaded workers.';
|
||||
|
||||
public function __construct(private readonly GuestNotificationService $guestNotifications)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(QueueManager $queueManager): int
|
||||
{
|
||||
$lockSeconds = (int) config('storage-monitor.queue_health.lock_seconds', 120);
|
||||
@@ -117,6 +126,8 @@ class CheckUploadQueuesCommand extends Command
|
||||
count($alerts)
|
||||
));
|
||||
|
||||
$this->maybeNotifyGuests($alerts);
|
||||
|
||||
return self::SUCCESS;
|
||||
} finally {
|
||||
if ($lock instanceof Lock) {
|
||||
@@ -125,6 +136,130 @@ class CheckUploadQueuesCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function maybeNotifyGuests(array $alerts): void
|
||||
{
|
||||
if (empty($alerts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dispatchPendingAlerts();
|
||||
$this->dispatchFailureAlerts();
|
||||
}
|
||||
|
||||
private function dispatchPendingAlerts(): void
|
||||
{
|
||||
$threshold = max(1, (int) config('storage-monitor.queue_health.pending_event_threshold', 5));
|
||||
$minutes = max(1, (int) config('storage-monitor.queue_health.pending_event_minutes', 8));
|
||||
|
||||
$pending = EventMediaAsset::query()
|
||||
->selectRaw('event_id, COUNT(*) as pending_count, MIN(created_at) as oldest_created_at')
|
||||
->where('status', 'pending')
|
||||
->where('created_at', '<=', now()->subMinutes($minutes))
|
||||
->groupBy('event_id')
|
||||
->havingRaw('COUNT(*) >= ?', [$threshold])
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
foreach ($pending as $row) {
|
||||
$event = Event::query()->find($row->event_id);
|
||||
|
||||
if (! $event) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = 'Uploads werden noch verarbeitet …';
|
||||
if ($this->recentlySentAlert($event->id, $title)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = (int) $row->pending_count;
|
||||
$body = $count > 1
|
||||
? sprintf('%d Fotos stehen noch in der Warteschlange. Wir sagen Bescheid, sobald alles gespeichert ist.', $count)
|
||||
: 'Ein Upload-Schub wird gerade verarbeitet. Danke für deine Geduld!';
|
||||
|
||||
$this->guestNotifications->createNotification(
|
||||
$event,
|
||||
GuestNotificationType::UPLOAD_ALERT,
|
||||
$title,
|
||||
$body,
|
||||
[
|
||||
'audience_scope' => GuestNotificationAudience::ALL,
|
||||
'priority' => 1,
|
||||
'expires_at' => now()->addMinutes(90),
|
||||
]
|
||||
);
|
||||
|
||||
$this->rememberAlert($event->id, $title);
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchFailureAlerts(): void
|
||||
{
|
||||
$threshold = max(1, (int) config('storage-monitor.queue_health.failed_event_threshold', 2));
|
||||
$minutes = max(1, (int) config('storage-monitor.queue_health.failed_event_minutes', 30));
|
||||
|
||||
$failed = EventMediaAsset::query()
|
||||
->selectRaw('event_id, COUNT(*) as failed_count')
|
||||
->where('status', 'failed')
|
||||
->where('updated_at', '>=', now()->subMinutes($minutes))
|
||||
->groupBy('event_id')
|
||||
->havingRaw('COUNT(*) >= ?', [$threshold])
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
foreach ($failed as $row) {
|
||||
$event = Event::query()->find($row->event_id);
|
||||
|
||||
if (! $event) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = 'Einige Uploads mussten neu gestartet werden';
|
||||
if ($this->recentlySentAlert($event->id, $title)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = (int) $row->failed_count;
|
||||
$body = $count > 1
|
||||
? sprintf('%d Fotos wurden automatisch erneut angestoßen. Bitte öffne kurz die App, falls deine Uploads hängen.', $count)
|
||||
: 'Ein Upload wurde neu gestartet. Öffne bitte kurz die App, damit nichts verloren geht.';
|
||||
|
||||
$this->guestNotifications->createNotification(
|
||||
$event,
|
||||
GuestNotificationType::SUPPORT_TIP,
|
||||
$title,
|
||||
$body,
|
||||
[
|
||||
'audience_scope' => GuestNotificationAudience::ALL,
|
||||
'priority' => 2,
|
||||
'expires_at' => now()->addHours(2),
|
||||
]
|
||||
);
|
||||
|
||||
$this->rememberAlert($event->id, $title);
|
||||
}
|
||||
}
|
||||
|
||||
private function recentlySentAlert(int $eventId, string $title): bool
|
||||
{
|
||||
$key = $this->alertCacheKey($eventId, $title);
|
||||
|
||||
return Cache::has($key);
|
||||
}
|
||||
|
||||
private function rememberAlert(int $eventId, string $title): void
|
||||
{
|
||||
$key = $this->alertCacheKey($eventId, $title);
|
||||
$ttl = max(5, (int) config('storage-monitor.queue_health.guest_alert_ttl', 30));
|
||||
|
||||
Cache::put($key, true, now()->addMinutes($ttl));
|
||||
}
|
||||
|
||||
private function alertCacheKey(int $eventId, string $title): string
|
||||
{
|
||||
return sprintf('guest-queue-alert:%d:%s', $eventId, sha1($title));
|
||||
}
|
||||
|
||||
private function readQueueSize(QueueManager $manager, ?string $connection, string $queue): int
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user