onQueue('feed-discovery'); } public function handle(LogSaver $logSaver, FetchFeedArticlesAction $fetchFeedArticles, NotificationService $notificationService): void { $logSaver->info('Starting feed article fetch', null, [ 'feed_id' => $this->feed->id, 'feed_name' => $this->feed->name, 'feed_url' => $this->feed->url, ]); $articles = $fetchFeedArticles->execute($this->feed); $logSaver->info('Feed article fetch completed', null, [ 'feed_id' => $this->feed->id, 'feed_name' => $this->feed->name, 'articles_count' => $articles->count(), ]); $this->feed->update(['last_fetched_at' => now()]); if ($articles->isEmpty()) { ActivityLogged::dispatch( ActivityTypeEnum::ERROR, "{$this->feed->name} returned no articles", ['articles_count' => 0], $this->feed, ); $this->warnFeedReturnedNothing($notificationService); return; } ActivityLogged::dispatch( ActivityTypeEnum::FETCH, "Fetched {$articles->count()} articles from {$this->feed->name}", ['articles_count' => $articles->count()], $this->feed, ); } private function warnFeedReturnedNothing(NotificationService $notificationService): void { $alreadyNotified = Notification::query() ->where('type', NotificationTypeEnum::FEED_EMPTY) ->where('notifiable_type', $this->feed->getMorphClass()) ->where('notifiable_id', $this->feed->getKey()) ->unread() ->exists(); if ($alreadyNotified) { return; } $notificationService->send( type: NotificationTypeEnum::FEED_EMPTY, severity: NotificationSeverityEnum::WARNING, title: "Feed \"{$this->feed->name}\" returned no articles", message: "The fetch completed but produced nothing. Check that {$this->feed->url} is still a valid feed.", notifiable: $this->feed, ); } public static function dispatchForAllActiveFeeds(): void { $logSaver = app(LogSaver::class); Feed::where('is_active', true) ->get() ->each(function (Feed $feed, $index) use ($logSaver) { // Space jobs apart to avoid overwhelming feeds $delayMinutes = $index * self::FEED_DISCOVERY_DELAY_MINUTES; self::dispatch($feed) ->delay(now()->addMinutes($delayMinutes)) ->onQueue('feed-discovery'); $logSaver->info('Dispatched feed discovery job', null, [ 'feed_id' => $feed->id, 'feed_name' => $feed->name, 'delay_minutes' => $delayMinutes, ]); }); } }