option('chunk')); $limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null; $reverse = (bool) $this->option('reverse'); $sync = (bool) $this->option('sync'); if ($sync) { return $this->handleSync($client, $chunk, $limit, $reverse); } return $this->handleQueue($chunk, $limit, $reverse); } // ── Queue mode (default) ────────────────────────────────────────────────── private function handleQueue(int $chunk, ?int $limit, bool $reverse): int { $uncapped = Artwork::query()->public()->published()->count(); $total = $limit !== null ? min($limit, $uncapped) : $uncapped; if ($total === 0) { $this->warn('No public, published artworks matched the rebuild query. Nothing was queued.'); return self::SUCCESS; } $estimatedChunks = (int) ceil($total / $chunk); $this->info(sprintf( 'Queueing Meilisearch rebuild for %d artwork(s) in %d chunk(s) of up to %d%s%s.', $total, $estimatedChunks, $chunk, $reverse ? ', newest first' : '', $limit !== null ? " (limit {$limit})" : '', )); $this->line('This command only dispatches queue jobs. Workers process the actual indexing asynchronously.'); $bar = $this->output->createProgressBar($total); $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); $bar->start(); $startedAt = microtime(true); $stats = $this->indexer->rebuildAll( $chunk, function (int $chunkNumber, int $chunkCount, int $dispatched, int $totalItems, int $firstId, int $lastId) use ($bar): void { $bar->advance($chunkCount); if ($this->output->isVerbose()) { $bar->clear(); $this->line(sprintf( 'Chunk %d queued %d artwork(s) [ids %d-%d] (%d/%d dispatched).', $chunkNumber, $chunkCount, $firstId, $lastId, $dispatched, $totalItems, )); $bar->display(); } }, $reverse, $limit, ); $bar->finish(); $this->newLine(2); $elapsed = microtime(true) - $startedAt; $this->info(sprintf( 'Queued %d artwork(s) across %d chunk(s) in %.2f seconds.', $stats['dispatched'], $stats['chunks'], $elapsed, )); $this->line('Workers will process the actual Meilisearch writes asynchronously.'); if ($this->output->isVerbose()) { $this->line('Tip: use -v for per-chunk output, or monitor Horizon/queue workers for completion.'); } return self::SUCCESS; } // ── Sync mode (--sync) ──────────────────────────────────────────────────── private function handleSync(MeilisearchClient $client, int $chunk, ?int $limit, bool $reverse): int { $this->info(sprintf( '[SYNC MODE] Writing directly to Meilisearch%s%s — no queue involved.', $reverse ? ', newest first' : '', $limit !== null ? ", limit {$limit}" : '', )); $this->newLine(); $query = Artwork::with([ 'user', 'group', 'tags', 'categories.contentType', 'stats', 'awardStat', ]) ->withoutGlobalScopes() // include non-public so we can report "why not" ->whereNotNull('id'); // all artworks if ($reverse) { $query->orderByDesc('id'); } else { $query->orderBy('id'); } if ($limit !== null) { $query->limit($limit); } $total = (clone $query)->count(); $indexed = 0; $removed = 0; $failed = 0; $processed = 0; $startedAt = microtime(true); $bar = $this->output->createProgressBar($total); $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'); $bar->start(); $query->chunk($chunk, function ($artworks) use ($client, $bar, &$indexed, &$removed, &$failed, &$processed): void { foreach ($artworks as $artwork) { $processed++; $id = (int) $artwork->id; $title = (string) ($artwork->title ?? '(no title)'); // Determine eligibility and reason $reasons = []; if (! $artwork->is_public) { $reasons[] = 'not public'; } if (! $artwork->is_approved) { $reasons[] = 'not approved'; } if ($artwork->published_at === null) { $reasons[] = 'not published'; } if ($artwork->deleted_at !== null) { $reasons[] = 'soft-deleted'; } $eligible = empty($reasons); try { $indexName = $artwork->searchableAs(); if ($eligible) { $document = $artwork->toSearchableArray(); $client->index($indexName)->addDocuments([$document]); $indexed++; $bar->clear(); $this->line(sprintf(' ✓ indexed #%d "%s"', $id, $title)); } else { $client->index($indexName)->deleteDocument($id); $removed++; $bar->clear(); $this->line(sprintf(' – removed #%d "%s" [%s]', $id, $title, implode(', ', $reasons))); } } catch (\Throwable $e) { $failed++; $bar->clear(); $this->line(sprintf(' ✗ failed #%d "%s" %s', $id, $title, $e->getMessage())); } $bar->advance(); } }); $bar->finish(); $this->newLine(2); $elapsed = microtime(true) - $startedAt; $this->info(sprintf( 'Done in %.2f s — %d indexed, %d removed from index, %d failed (of %d processed).', $elapsed, $indexed, $removed, $failed, $processed, )); return $failed > 0 ? self::FAILURE : self::SUCCESS; } }