option('dry-run'); $limit = (int) $this->option('limit'); $now = now()->utc(); $candidates = Artwork::query() ->where('artwork_status', 'scheduled') ->where('publish_at', '<=', $now) ->where('is_approved', true) ->orderBy('publish_at') ->limit($limit) ->get(['id', 'user_id', 'title', 'publish_at', 'artwork_status']); if ($candidates->isEmpty()) { $this->line('No scheduled artworks due for publishing.'); return self::SUCCESS; } $this->info("Found {$candidates->count()} artwork(s) to publish." . ($dryRun ? ' [DRY RUN]' : '')); $published = 0; $errors = 0; foreach ($candidates as $candidate) { if ($dryRun) { $this->line(" [dry-run] Would publish artwork #{$candidate->id}: \"{$candidate->title}\""); continue; } try { DB::transaction(function () use ($candidate, $now, &$published) { // Re-fetch with lock to avoid double-publish in concurrent runs $artwork = Artwork::query() ->lockForUpdate() ->where('id', $candidate->id) ->where('artwork_status', 'scheduled') ->first(); if (! $artwork) { // Already published or status changed – skip return; } $artwork->is_public = $artwork->visibility !== Artwork::VISIBILITY_PRIVATE; $artwork->published_at = $now; $artwork->artwork_status = 'published'; $artwork->save(); // Trigger Meilisearch reindex via Scout (if searchable trait present) if (method_exists($artwork, 'searchable')) { try { $artwork->searchable(); } catch (\Throwable $e) { Log::warning("PublishScheduled: scout reindex failed for #{$artwork->id}: {$e->getMessage()}"); } } // Record activity event try { ActivityEvent::record( actorId: (int) $artwork->user_id, type: ActivityEvent::TYPE_UPLOAD, targetType: ActivityEvent::TARGET_ARTWORK, targetId: (int) $artwork->id, ); } catch (\Throwable) {} try { app(UserActivityService::class)->logUpload((int) $artwork->user_id, (int) $artwork->id); } catch (\Throwable) {} $published++; $this->line(" Published artwork #{$artwork->id}: \"{$artwork->title}\""); }); } catch (\Throwable $e) { $errors++; Log::error("PublishScheduledArtworksCommand: failed to publish artwork #{$candidate->id}: {$e->getMessage()}"); $this->error(" Failed to publish #{$candidate->id}: {$e->getMessage()}"); } } if (! $dryRun) { $this->info("Done. Published: {$published}, Errors: {$errors}."); } return $errors > 0 ? self::FAILURE : self::SUCCESS; } }