trypost/app/Console/Commands/RecoverStuckPosts.php

68 lines
2.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Enums\Post\Status as PostStatus;
use App\Enums\PostPlatform\Status as PlatformStatus;
use App\Models\Post;
use Illuminate\Console\Command;
class RecoverStuckPosts extends Command
{
protected $signature = 'social:recover-stuck-posts';
protected $description = 'Recover posts stuck in publishing status for more than 1 hour';
public function handle(): void
{
$count = 0;
Post::query()
->where('status', PostStatus::Publishing)
->where('updated_at', '<=', now()->subHour())
->each(function (Post $post) use (&$count) {
$post->postPlatforms()
->where('enabled', true)
fix: Pinterest video processing timeout — longer poll + retry (#246) * fix: give Pinterest video processing more time and retry on timeout A valid ~54s video pin failed after ~90s of polling while Pinterest was still processing. Extend the poll window to ~5 minutes and treat timeout as platform unavailable so PublishToSocialPlatform reschedules instead of failing the post on the first attempt. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: use Laravel Sleep for Pinterest media processing polls Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: inline Pinterest video processing poll constants Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: map Pinterest media upload statuses to an enum Use the official MediaUploadStatus values (registered, processing, succeeded, failed) instead of comparing raw strings in the publisher. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: trim Pinterest media processing docblock * fix: cap platform-unavailable retries and recover stuck retrying posts Stop infinite reschedules after 6 attempts with a user-safe failure message, keep technical detail in error_context, recover Retrying platforms in social:recover-stuck-posts, and drop unused isTerminal(). Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unused failedCount in RecoverStuckPosts Co-authored-by: Cursor <cursoragent@cursor.com> * fix: skip final Pinterest poll sleep and localize recover timeout Co-authored-by: Cursor <cursoragent@cursor.com> * fix: raise publish job timeout headroom and ignore already-failed platforms Give social publish jobs 15 minutes so Pinterest media polling fits under the worker limit, bump Horizon/redis retry_after above that timeout, and skip handle/failed when the platform is already Failed so delayed jobs cannot revive posts recovered by social:recover-stuck-posts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore social-publishing and ai-assistant horizon supervisors Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden Pinterest 401 handling, unique publish jobs, and recover JSON Treat media-status 401 as TokenExpired, make PublishToSocialPlatform unique per platform+attempt so retries still queue, and persist recover error_context via Eloquent casts instead of manual json_encode. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: mass-update stuck post platforms without per-row each Eloquent query updates already bind JSON arrays correctly here, so one UPDATE is enough — no manual json_encode and no N model writes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify Pinterest media processing poll loop Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify publish job retry and terminal status checks Co-authored-by: Cursor <cursoragent@cursor.com> * fix: do not finalize posts while platforms are still retrying Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover Pinterest timeout, unique jobs, and recover edge cases Co-authored-by: Cursor <cursoragent@cursor.com> * fix: retry Pinterest media poll on connection errors and tighten tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove ineffective TypeError import that breaks CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 23:49:37 +00:00
->whereIn('status', [PlatformStatus::Publishing, PlatformStatus::Pending, PlatformStatus::Retrying])
->where('updated_at', '<=', now()->subHour())
->update([
'status' => PlatformStatus::Failed,
fix: Pinterest video processing timeout — longer poll + retry (#246) * fix: give Pinterest video processing more time and retry on timeout A valid ~54s video pin failed after ~90s of polling while Pinterest was still processing. Extend the poll window to ~5 minutes and treat timeout as platform unavailable so PublishToSocialPlatform reschedules instead of failing the post on the first attempt. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: use Laravel Sleep for Pinterest media processing polls Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: inline Pinterest video processing poll constants Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: map Pinterest media upload statuses to an enum Use the official MediaUploadStatus values (registered, processing, succeeded, failed) instead of comparing raw strings in the publisher. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: trim Pinterest media processing docblock * fix: cap platform-unavailable retries and recover stuck retrying posts Stop infinite reschedules after 6 attempts with a user-safe failure message, keep technical detail in error_context, recover Retrying platforms in social:recover-stuck-posts, and drop unused isTerminal(). Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unused failedCount in RecoverStuckPosts Co-authored-by: Cursor <cursoragent@cursor.com> * fix: skip final Pinterest poll sleep and localize recover timeout Co-authored-by: Cursor <cursoragent@cursor.com> * fix: raise publish job timeout headroom and ignore already-failed platforms Give social publish jobs 15 minutes so Pinterest media polling fits under the worker limit, bump Horizon/redis retry_after above that timeout, and skip handle/failed when the platform is already Failed so delayed jobs cannot revive posts recovered by social:recover-stuck-posts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore social-publishing and ai-assistant horizon supervisors Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden Pinterest 401 handling, unique publish jobs, and recover JSON Treat media-status 401 as TokenExpired, make PublishToSocialPlatform unique per platform+attempt so retries still queue, and persist recover error_context via Eloquent casts instead of manual json_encode. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: mass-update stuck post platforms without per-row each Eloquent query updates already bind JSON arrays correctly here, so one UPDATE is enough — no manual json_encode and no N model writes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify Pinterest media processing poll loop Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify publish job retry and terminal status checks Co-authored-by: Cursor <cursoragent@cursor.com> * fix: do not finalize posts while platforms are still retrying Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover Pinterest timeout, unique jobs, and recover edge cases Co-authored-by: Cursor <cursoragent@cursor.com> * fix: retry Pinterest media poll on connection errors and tighten tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove ineffective TypeError import that breaks CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 23:49:37 +00:00
'error_message' => __('posts.errors.publishing_timed_out'),
'error_context' => [
'category' => 'timeout',
'failed_at' => now()->toIso8601String(),
fix: Pinterest video processing timeout — longer poll + retry (#246) * fix: give Pinterest video processing more time and retry on timeout A valid ~54s video pin failed after ~90s of polling while Pinterest was still processing. Extend the poll window to ~5 minutes and treat timeout as platform unavailable so PublishToSocialPlatform reschedules instead of failing the post on the first attempt. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: use Laravel Sleep for Pinterest media processing polls Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: inline Pinterest video processing poll constants Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: map Pinterest media upload statuses to an enum Use the official MediaUploadStatus values (registered, processing, succeeded, failed) instead of comparing raw strings in the publisher. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: trim Pinterest media processing docblock * fix: cap platform-unavailable retries and recover stuck retrying posts Stop infinite reschedules after 6 attempts with a user-safe failure message, keep technical detail in error_context, recover Retrying platforms in social:recover-stuck-posts, and drop unused isTerminal(). Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unused failedCount in RecoverStuckPosts Co-authored-by: Cursor <cursoragent@cursor.com> * fix: skip final Pinterest poll sleep and localize recover timeout Co-authored-by: Cursor <cursoragent@cursor.com> * fix: raise publish job timeout headroom and ignore already-failed platforms Give social publish jobs 15 minutes so Pinterest media polling fits under the worker limit, bump Horizon/redis retry_after above that timeout, and skip handle/failed when the platform is already Failed so delayed jobs cannot revive posts recovered by social:recover-stuck-posts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore social-publishing and ai-assistant horizon supervisors Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden Pinterest 401 handling, unique publish jobs, and recover JSON Treat media-status 401 as TokenExpired, make PublishToSocialPlatform unique per platform+attempt so retries still queue, and persist recover error_context via Eloquent casts instead of manual json_encode. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: mass-update stuck post platforms without per-row each Eloquent query updates already bind JSON arrays correctly here, so one UPDATE is enough — no manual json_encode and no N model writes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify Pinterest media processing poll loop Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify publish job retry and terminal status checks Co-authored-by: Cursor <cursoragent@cursor.com> * fix: do not finalize posts while platforms are still retrying Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover Pinterest timeout, unique jobs, and recover edge cases Co-authored-by: Cursor <cursoragent@cursor.com> * fix: retry Pinterest media poll on connection errors and tighten tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove ineffective TypeError import that breaks CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 23:49:37 +00:00
],
]);
fix: Pinterest video processing timeout — longer poll + retry (#246) * fix: give Pinterest video processing more time and retry on timeout A valid ~54s video pin failed after ~90s of polling while Pinterest was still processing. Extend the poll window to ~5 minutes and treat timeout as platform unavailable so PublishToSocialPlatform reschedules instead of failing the post on the first attempt. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: use Laravel Sleep for Pinterest media processing polls Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: inline Pinterest video processing poll constants Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: map Pinterest media upload statuses to an enum Use the official MediaUploadStatus values (registered, processing, succeeded, failed) instead of comparing raw strings in the publisher. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: trim Pinterest media processing docblock * fix: cap platform-unavailable retries and recover stuck retrying posts Stop infinite reschedules after 6 attempts with a user-safe failure message, keep technical detail in error_context, recover Retrying platforms in social:recover-stuck-posts, and drop unused isTerminal(). Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: remove unused failedCount in RecoverStuckPosts Co-authored-by: Cursor <cursoragent@cursor.com> * fix: skip final Pinterest poll sleep and localize recover timeout Co-authored-by: Cursor <cursoragent@cursor.com> * fix: raise publish job timeout headroom and ignore already-failed platforms Give social publish jobs 15 minutes so Pinterest media polling fits under the worker limit, bump Horizon/redis retry_after above that timeout, and skip handle/failed when the platform is already Failed so delayed jobs cannot revive posts recovered by social:recover-stuck-posts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: restore social-publishing and ai-assistant horizon supervisors Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden Pinterest 401 handling, unique publish jobs, and recover JSON Treat media-status 401 as TokenExpired, make PublishToSocialPlatform unique per platform+attempt so retries still queue, and persist recover error_context via Eloquent casts instead of manual json_encode. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: mass-update stuck post platforms without per-row each Eloquent query updates already bind JSON arrays correctly here, so one UPDATE is enough — no manual json_encode and no N model writes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify Pinterest media processing poll loop Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify publish job retry and terminal status checks Co-authored-by: Cursor <cursoragent@cursor.com> * fix: do not finalize posts while platforms are still retrying Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover Pinterest timeout, unique jobs, and recover edge cases Co-authored-by: Cursor <cursoragent@cursor.com> * fix: retry Pinterest media poll on connection errors and tighten tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove ineffective TypeError import that breaks CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 23:49:37 +00:00
// 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()
->where('enabled', true)
->whereIn('status', [PlatformStatus::Publishing, PlatformStatus::Pending, PlatformStatus::Retrying])
->exists();
if ($stillActive) {
return;
}
$enabledPlatforms = $post->postPlatforms()->where('enabled', true)->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.");
}
}