From efb9db3eed671b6ac66902011e4b849731fa4f45 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 4 Nov 2025 17:24:04 +1100 Subject: [PATCH] StatusPipeline defensive checks --- app/Jobs/StatusPipeline/NewStatusPipeline.php | 19 ++++++- .../StatusPipeline/RemoteStatusDelete.php | 7 +++ .../StatusActivityPubDeliver.php | 13 +++++ app/Jobs/StatusPipeline/StatusDelete.php | 16 +++++- app/Jobs/StatusPipeline/StatusEntityLexer.php | 20 ++++++- ...sLocalUpdateActivityPubDeliverPipeline.php | 13 +++++ .../StatusRemoteUpdatePipeline.php | 52 ++++++++++++++----- .../StatusPipeline/StatusReplyPipeline.php | 27 +++++++++- .../StatusPipeline/StatusTagsPipeline.php | 13 +++++ 9 files changed, 160 insertions(+), 20 deletions(-) diff --git a/app/Jobs/StatusPipeline/NewStatusPipeline.php b/app/Jobs/StatusPipeline/NewStatusPipeline.php index e65b7869d..d358a37cf 100644 --- a/app/Jobs/StatusPipeline/NewStatusPipeline.php +++ b/app/Jobs/StatusPipeline/NewStatusPipeline.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Redis; class NewStatusPipeline implements ShouldQueue @@ -44,7 +45,15 @@ class NewStatusPipeline implements ShouldQueue */ public function handle() { - if (!Status::where('id', $this->status->id)->exists()) { + $status = $this->status; + + // Verify status exists + if (!$status) { + Log::info("NewStatusPipeline: Status no longer exists, skipping job"); + return; + } + + if (!Status::where('id', $status->id)->exists()) { // The status has already been deleted by the time the job is running // Don't publish the status, and just no-op return; @@ -61,6 +70,12 @@ class NewStatusPipeline implements ShouldQueue return; } } - StatusEntityLexer::dispatch($this->status); + + try { + StatusEntityLexer::dispatch($status); + } catch (\Exception $e) { + Log::warning("NewStatusPipeline: Failed to dispatch StatusEntityLexer for status {$status->id}: " . $e->getMessage()); + throw $e; + } } } diff --git a/app/Jobs/StatusPipeline/RemoteStatusDelete.php b/app/Jobs/StatusPipeline/RemoteStatusDelete.php index 2c3f70777..e5e6b3cbf 100644 --- a/app/Jobs/StatusPipeline/RemoteStatusDelete.php +++ b/app/Jobs/StatusPipeline/RemoteStatusDelete.php @@ -29,6 +29,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; class RemoteStatusDelete implements ShouldBeUniqueUntilProcessing, ShouldQueue { @@ -95,6 +96,12 @@ class RemoteStatusDelete implements ShouldBeUniqueUntilProcessing, ShouldQueue { $status = $this->status; + // Verify status exists + if (!$status) { + Log::info("RemoteStatusDelete: Status no longer exists, skipping job"); + return; + } + if ($status->deleted_at) { return; } diff --git a/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php index 23b79c1c2..19ea1df76 100644 --- a/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php +++ b/app/Jobs/StatusPipeline/StatusActivityPubDeliver.php @@ -51,8 +51,21 @@ class StatusActivityPubDeliver implements ShouldQueue public function handle() { $status = $this->status; + + // Verify status exists + if (!$status) { + Log::info("StatusActivityPubDeliver: Status no longer exists, skipping job"); + return; + } + $profile = $status->profile; + // Verify profile exists + if (!$profile) { + Log::info("StatusActivityPubDeliver: Profile no longer exists for status {$status->id}, skipping job"); + return; + } + // ignore group posts // if($status->group_id != null) { // return; diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index d85ebdc4a..5e38710be 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/app/Jobs/StatusPipeline/StatusDelete.php @@ -30,6 +30,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; @@ -68,7 +69,20 @@ class StatusDelete implements ShouldQueue public function handle() { $status = $this->status; - $profile = $this->status->profile; + + // Verify status exists + if (!$status) { + Log::info("StatusDelete: Status no longer exists, skipping job"); + return; + } + + $profile = $status->profile; + + // Verify profile exists + if (!$profile) { + Log::info("StatusDelete: Profile no longer exists for status {$status->id}, skipping job"); + return; + } StatusService::del($status->id, true); if ($profile) { diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index 8fe767417..e5e3989a8 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -23,6 +23,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; class StatusEntityLexer implements ShouldQueue { @@ -58,9 +59,26 @@ class StatusEntityLexer implements ShouldQueue */ public function handle() { - $profile = $this->status->profile; $status = $this->status; + // Verify status exists + if (!$status) { + Log::info("StatusEntityLexer: Status no longer exists, skipping job"); + return; + } + + // Verify status has a profile + if (!$status->profile_id) { + Log::info("StatusEntityLexer: Status {$status->id} has no profile_id, skipping job"); + return; + } + + $profile = $status->profile; + if (!$profile) { + Log::info("StatusEntityLexer: Profile no longer exists for status {$status->id}, skipping job"); + return; + } + if (in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { $profile->status_count = $profile->status_count + 1; $profile->save(); diff --git a/app/Jobs/StatusPipeline/StatusLocalUpdateActivityPubDeliverPipeline.php b/app/Jobs/StatusPipeline/StatusLocalUpdateActivityPubDeliverPipeline.php index b08baa0d4..047c392af 100644 --- a/app/Jobs/StatusPipeline/StatusLocalUpdateActivityPubDeliverPipeline.php +++ b/app/Jobs/StatusPipeline/StatusLocalUpdateActivityPubDeliverPipeline.php @@ -49,8 +49,21 @@ class StatusLocalUpdateActivityPubDeliverPipeline implements ShouldQueue public function handle() { $status = $this->status; + + // Verify status exists + if (!$status) { + Log::info("StatusLocalUpdateActivityPubDeliverPipeline: Status no longer exists, skipping job"); + return; + } + $profile = $status->profile; + // Verify profile exists + if (!$profile) { + Log::info("StatusLocalUpdateActivityPubDeliverPipeline: Profile no longer exists for status {$status->id}, skipping job"); + return; + } + // ignore group posts // if($status->group_id != null) { // return; diff --git a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php index ff2606477..18b48aeca 100644 --- a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php +++ b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php @@ -15,6 +15,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; use Purify; class StatusRemoteUpdatePipeline implements ShouldQueue @@ -37,28 +38,51 @@ class StatusRemoteUpdatePipeline implements ShouldQueue public function handle(): void { $activity = $this->activity; + + // Verify activity exists and has required fields + if (!$activity) { + Log::info("StatusRemoteUpdatePipeline: Activity not found, skipping job"); + return; + } + if (!isset($activity['id'])) { + Log::info("StatusRemoteUpdatePipeline: Invalid activity data, skipping job"); + return; + } + $status = Status::with('media')->whereObjectUrl($activity['id'])->first(); if (! $status) { + Log::info("StatusRemoteUpdatePipeline: Status not found for activity {$activity['id']}, skipping job"); return; } - $this->createPreviousEdit($status); - $this->updateMedia($status, $activity); - $this->updateImmediateAttributes($status, $activity); - $this->createEdit($status, $activity); + + try { + $this->createPreviousEdit($status); + $this->updateMedia($status, $activity); + $this->updateImmediateAttributes($status, $activity); + $this->createEdit($status, $activity); + } catch (\Exception $e) { + Log::warning("StatusRemoteUpdatePipeline: Failed to update status {$status->id}: " . $e->getMessage()); + throw $e; + } } protected function createPreviousEdit($status) { - if (! $status->edits()->count()) { - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $status->caption, - 'spoiler_text' => $status->cw_summary, - 'is_nsfw' => $status->is_nsfw, - 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), - 'created_at' => $status->created_at, - ]); + try { + if (! $status->edits()->count()) { + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $status->caption, + 'spoiler_text' => $status->cw_summary, + 'is_nsfw' => $status->is_nsfw, + 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), + 'created_at' => $status->created_at, + ]); + } + } catch (\Exception $e) { + Log::warning("StatusRemoteUpdatePipeline: Failed to create previous edit for status {$status->id}: " . $e->getMessage()); + throw $e; } } diff --git a/app/Jobs/StatusPipeline/StatusReplyPipeline.php b/app/Jobs/StatusPipeline/StatusReplyPipeline.php index d8af7b96b..5a3f3ba2e 100644 --- a/app/Jobs/StatusPipeline/StatusReplyPipeline.php +++ b/app/Jobs/StatusPipeline/StatusReplyPipeline.php @@ -11,6 +11,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Redis; use App\Services\NotificationService; use App\Services\StatusService; @@ -49,14 +50,36 @@ class StatusReplyPipeline implements ShouldQueue public function handle() { $status = $this->status; + + // Verify status exists + if (!$status) { + Log::info("StatusReplyPipeline: Status no longer exists, skipping job"); + return 1; + } + + // Verify status is a reply + if (!$status->in_reply_to_id) { + Log::info("StatusReplyPipeline: Status {$status->id} is not a reply, skipping job"); + return 1; + } + $actor = $status->profile; - $reply = Status::find($status->in_reply_to_id); + if (!$actor) { + Log::info("StatusReplyPipeline: Actor profile no longer exists for status {$status->id}, skipping job"); + return 1; + } - if(!$actor || !$reply) { + $reply = Status::find($status->in_reply_to_id); + if (!$reply) { + Log::info("StatusReplyPipeline: Reply status {$status->in_reply_to_id} no longer exists for status {$status->id}, skipping job"); return 1; } $target = $reply->profile; + if (!$target) { + Log::info("StatusReplyPipeline: Target profile no longer exists for reply {$reply->id}, skipping job"); + return 1; + } $exists = Notification::whereProfileId($target->id) ->whereActorId($actor->id) diff --git a/app/Jobs/StatusPipeline/StatusTagsPipeline.php b/app/Jobs/StatusPipeline/StatusTagsPipeline.php index 895c3593d..a70180246 100644 --- a/app/Jobs/StatusPipeline/StatusTagsPipeline.php +++ b/app/Jobs/StatusPipeline/StatusTagsPipeline.php @@ -17,6 +17,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; class StatusTagsPipeline implements ShouldQueue { @@ -47,6 +48,18 @@ class StatusTagsPipeline implements ShouldQueue $res = $this->activity; $status = $this->status; + // Verify status exists + if (!$status) { + Log::info("StatusTagsPipeline: Status no longer exists, skipping job"); + return; + } + + // Verify activity data exists + if (!$res || !isset($res['tag'])) { + Log::info("StatusTagsPipeline: No tag data in activity for status {$status->id}, skipping job"); + return; + } + if (isset($res['tag']['type'], $res['tag']['name'])) { $res['tag'] = [$res['tag']]; }