where('status', PostStatus::Publishing) ->where('updated_at', '<=', now()->subHour()) ->each(function (Post $post) use (&$count) { $stalePlatforms = $post->postPlatforms() ->enabled() ->whereIn('status', [PlatformStatus::Publishing, PlatformStatus::Pending, PlatformStatus::Retrying]) ->where('updated_at', '<=', now()->subHour()) ->get(); $stalePlatforms->each(function (PostPlatform $postPlatform): void { $this->tiktokPhotoDerivativeCleaner->cleanupUnlessPublishInFlight( $postPlatform->error_context, $postPlatform->id, ); $postPlatform->update([ 'status' => PlatformStatus::Failed, 'error_message' => __('posts.errors.publishing_timed_out'), 'error_context' => [ ...($postPlatform->error_context ?? []), 'category' => ErrorCategory::Timeout->value, 'failed_at' => now()->toIso8601String(), ], ]); }); // Delayed platform-unavailable retries keep the platform Retrying with a // fresh updated_at — do not finalize the post while that work is still live. $stillActive = $post->postPlatforms() ->enabled() ->whereIn('status', [PlatformStatus::Publishing, PlatformStatus::Pending, PlatformStatus::Retrying]) ->exists(); if ($stillActive) { return; } $enabledPlatforms = $post->postPlatforms()->enabled()->get(); $total = $enabledPlatforms->count(); $publishedCount = $enabledPlatforms->where('status', PlatformStatus::Published)->count(); if ($publishedCount === $total) { $post->markAsPublished(); } elseif ($publishedCount > 0) { $post->markAsPartiallyPublished(); } else { $post->markAsFailed(); } $count++; }); $this->info("Recovered {$count} stuck posts."); } }