fedi-feed-router/app/Jobs/ArticleDiscoveryForFeedJob.php

112 lines
3.5 KiB
PHP
Raw Normal View History

2025-07-05 18:26:04 +02:00
<?php
namespace App\Jobs;
use App\Actions\FetchFeedArticlesAction;
use App\Enums\ActivityTypeEnum;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Events\ActivityLogged;
2025-07-05 18:26:04 +02:00
use App\Models\Feed;
use App\Models\Notification;
2025-07-05 18:26:04 +02:00
use App\Services\Log\LogSaver;
use App\Services\Notification\NotificationService;
2025-07-05 18:26:04 +02:00
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class ArticleDiscoveryForFeedJob implements ShouldQueue
{
use Queueable;
private const FEED_DISCOVERY_DELAY_MINUTES = 5;
public function __construct(
private readonly Feed $feed
) {
$this->onQueue('feed-discovery');
}
public function handle(LogSaver $logSaver, FetchFeedArticlesAction $fetchFeedArticles, NotificationService $notificationService): void
2025-07-05 18:26:04 +02:00
{
2025-08-15 02:50:42 +02:00
$logSaver->info('Starting feed article fetch', null, [
2025-07-05 18:26:04 +02:00
'feed_id' => $this->feed->id,
'feed_name' => $this->feed->name,
'feed_url' => $this->feed->url,
2025-07-05 18:26:04 +02:00
]);
$articles = $fetchFeedArticles->execute($this->feed);
2025-07-05 18:26:04 +02:00
2025-08-15 02:50:42 +02:00
$logSaver->info('Feed article fetch completed', null, [
2025-07-05 18:26:04 +02:00
'feed_id' => $this->feed->id,
'feed_name' => $this->feed->name,
'articles_count' => $articles->count(),
2025-07-05 18:26:04 +02:00
]);
$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,
);
2025-07-05 18:26:04 +02:00
}
public static function dispatchForAllActiveFeeds(): void
{
2025-08-15 02:50:42 +02:00
$logSaver = app(LogSaver::class);
2025-07-05 18:26:04 +02:00
Feed::where('is_active', true)
->get()
2025-08-15 02:50:42 +02:00
->each(function (Feed $feed, $index) use ($logSaver) {
2025-07-05 18:26:04 +02:00
// Space jobs apart to avoid overwhelming feeds
$delayMinutes = $index * self::FEED_DISCOVERY_DELAY_MINUTES;
self::dispatch($feed)
->delay(now()->addMinutes($delayMinutes))
->onQueue('feed-discovery');
2025-08-15 02:50:42 +02:00
$logSaver->info('Dispatched feed discovery job', null, [
2025-07-05 18:26:04 +02:00
'feed_id' => $feed->id,
'feed_name' => $feed->name,
'delay_minutes' => $delayMinutes,
2025-07-05 18:26:04 +02:00
]);
});
}
}