Scheduling Job instead of event

This commit is contained in:
myrmidex 2025-08-12 20:16:44 +02:00
parent 8c28f09921
commit 5c00149e66
7 changed files with 80 additions and 132 deletions

View file

@ -1,18 +0,0 @@
<?php
namespace App\Events;
use App\Models\Article;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ArticleReadyToPublish
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public Article $article)
{
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace App\Jobs;
use App\Exceptions\PublishException;
use App\Models\Article;
use App\Services\Article\ArticleFetcher;
use App\Services\Publishing\ArticlePublishingService;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class PublishNextArticleJob implements ShouldQueue, ShouldBeUnique
{
use Queueable;
/**
* The number of seconds after which the job's unique lock will be released.
*/
public int $uniqueFor = 300;
public function __construct()
{
$this->onQueue('publishing');
}
/**
* Execute the job.
* @throws PublishException
*/
public function handle(): void
{
// Get the oldest approved article that hasn't been published yet
$article = Article::where('is_approved', true)
->where('is_valid', true)
->whereDoesntHave('articlePublication')
->oldest('approved_at')
->first();
if (! $article) {
return;
}
logger()->info('Publishing next article from scheduled job', [
'article_id' => $article->id,
'title' => $article->title,
'url' => $article->url,
'approved_at' => $article->approved_at
]);
// Fetch article data
$extractedData = ArticleFetcher::fetchArticleData($article);
/** @var ArticlePublishingService $publishingService */
$publishingService = resolve(ArticlePublishingService::class);
try {
$publishingService->publishToRoutedChannels($article, $extractedData);
logger()->info('Successfully published article', [
'article_id' => $article->id,
'title' => $article->title
]);
} catch (PublishException $e) {
logger()->error('Failed to publish article', [
'article_id' => $article->id,
'error' => $e->getMessage()
]);
throw $e;
}
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Jobs;
use App\Exceptions\PublishException;
use App\Models\Article;
use App\Services\Article\ArticleFetcher;
use App\Services\Publishing\ArticlePublishingService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class PublishToLemmyJob implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public array $backoff = [60, 120, 300];
public function __construct(
private readonly Article $article
) {
$this->onQueue('publishing');
}
public function handle(): void
{
$extractedData = ArticleFetcher::fetchArticleData($this->article);
/** @var ArticlePublishingService $publishingService */
$publishingService = resolve(ArticlePublishingService::class);
try {
$publishingService->publishToRoutedChannels($this->article, $extractedData);
} catch (PublishException $e) {
$this->fail($e);
}
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Listeners;
use App\Events\ArticleApproved;
use App\Events\ArticleReadyToPublish;
use Illuminate\Contracts\Queue\ShouldQueue;
class PublishApprovedArticle implements ShouldQueue
{
public string $queue = 'default';
public function handle(ArticleApproved $event): void
{
$article = $event->article;
// Skip if already has publication (prevents duplicate processing)
if ($article->articlePublication()->exists()) {
return;
}
// Only publish if the article is valid and approved
if ($article->isValid() && $article->isApproved()) {
event(new ArticleReadyToPublish($article));
}
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace App\Listeners;
use App\Events\ArticleReadyToPublish;
use App\Jobs\PublishToLemmyJob;
use Illuminate\Contracts\Queue\ShouldQueue;
class PublishArticle implements ShouldQueue
{
public string|null $queue = 'publishing';
public int $delay = 300;
public int $tries = 3;
public int $backoff = 300;
public function __construct()
{}
public function handle(ArticleReadyToPublish $event): void
{
$article = $event->article;
if ($article->articlePublication()->exists()) {
logger()->info('Article already published, skipping job dispatch', [
'article_id' => $article->id,
'url' => $article->url
]);
return;
}
logger()->info('Article queued for publishing to Lemmy', [
'article_id' => $article->id,
'url' => $article->url
]);
PublishToLemmyJob::dispatch($article);
}
}

View file

@ -30,16 +30,6 @@ public function boot(): void
\App\Listeners\ValidateArticleListener::class, \App\Listeners\ValidateArticleListener::class,
); );
Event::listen(
\App\Events\ArticleApproved::class,
\App\Listeners\PublishApprovedArticle::class,
);
Event::listen(
\App\Events\ArticleReadyToPublish::class,
\App\Listeners\PublishArticle::class,
);
app()->make(ExceptionHandler::class) app()->make(ExceptionHandler::class)
->reportable(function (Throwable $e) { ->reportable(function (Throwable $e) {

View file

@ -1,8 +1,15 @@
<?php <?php
use App\Jobs\PublishNextArticleJob;
use App\Jobs\SyncChannelPostsJob; use App\Jobs\SyncChannelPostsJob;
use Illuminate\Support\Facades\Schedule; use Illuminate\Support\Facades\Schedule;
Schedule::call(function () { Schedule::call(function () {
SyncChannelPostsJob::dispatchForAllActiveChannels(); SyncChannelPostsJob::dispatchForAllActiveChannels();
})->everyTenMinutes()->name('sync-lemmy-channel-posts'); })->everyTenMinutes()->name('sync-lemmy-channel-posts');
Schedule::job(new PublishNextArticleJob)
->everyFiveMinutes()
->name('publish-next-article')
->withoutOverlapping()
->onOneServer();