Guard PublishPostJob against duplicate/stale dispatches

Skip if the post is no longer status=publishing when the job runs, so
a stray duplicate (e.g. clicking Publish twice while no worker was
running) can't publish the same post to WordPress a second time.

Also root-caused a stuck-in-"publishing" post reported live: the
queue worker started manually last session was a bare background
process, not a real service, and had died. Replaced it with a
systemd unit (muninn-queue-worker.service, enabled + running) so
the Publish button keeps working across reboots/restarts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
m105
2026-07-21 19:29:44 +03:00
co-authored by Claude Sonnet 5
parent 240fd3c3f6
commit fd185ab813
2 changed files with 21 additions and 0 deletions
+7
View File
@@ -27,6 +27,13 @@ class PublishPostJob implements ShouldQueue
return; return;
} }
// A duplicate/stale job for a post already resolved by an earlier
// run of this same job (e.g. a double-dispatch while no worker was
// running) — skip it rather than risk publishing twice.
if ($post->status !== 'publishing') {
return;
}
$publisher = $publishers->resolve($post); $publisher = $publishers->resolve($post);
if (! $publisher) { if (! $publisher) {
+14
View File
@@ -60,6 +60,20 @@ test('it reverts to draft when no publisher supports the post type', function ()
->publish_error->toContain('No publisher available'); ->publish_error->toContain('No publisher available');
}); });
test('it skips a duplicate job for a post no longer in publishing state', function () {
Http::fake([
'*/wp-json/wp/v2/posts' => Http::response(['link' => 'https://wp.example.com/post'], 201),
]);
$site = Site::factory()->wordpress()->create();
$post = Post::factory()->create(['site_id' => $site->id, 'type' => 'blog', 'title' => 'Hi', 'status' => 'published', 'published_url' => 'https://wp.example.com/already-there']);
(new PublishPostJob($post->id))->handle(app(PublisherManager::class));
Http::assertNothingSent();
expect($post->fresh()->published_url)->toBe('https://wp.example.com/already-there');
});
test('it publishes a reddit post via the sidecar', function () { test('it publishes a reddit post via the sidecar', function () {
Process::fake([ Process::fake([
'*' => Process::result(output: json_encode(['success' => true, 'url' => 'https://reddit.com/r/test/comments/abc'])), '*' => Process::result(output: json_encode(['success' => true, 'url' => 'https://reddit.com/r/test/comments/abc'])),