Wire admin studio SSR and search infrastructure
This commit is contained in:
181
app/Console/Commands/ForceIndexArtworkCommand.php
Normal file
181
app/Console/Commands/ForceIndexArtworkCommand.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Meilisearch\Client as MeilisearchClient;
|
||||
|
||||
/**
|
||||
* Directly write a single artwork into the Meilisearch index, bypassing the queue.
|
||||
*
|
||||
* Useful when:
|
||||
* - A rebuild was run but the queue worker was not consuming the `search` queue.
|
||||
* - A specific artwork is missing from the live index and you want it visible immediately.
|
||||
* - You need to force-push a corrected document after schema changes.
|
||||
*
|
||||
* Usage:
|
||||
* php artisan artworks:search-force-index 69810
|
||||
* php artisan artworks:search-force-index # interactive prompt
|
||||
* php artisan artworks:search-force-index 69810 --dry-run
|
||||
* php artisan artworks:search-force-index 69810 --force # index even if not public/approved/published
|
||||
*/
|
||||
final class ForceIndexArtworkCommand extends Command
|
||||
{
|
||||
protected $signature = 'artworks:search-force-index
|
||||
{artwork_id? : The artwork ID to force-index}
|
||||
{--index= : Override the Meilisearch index name}
|
||||
{--dry-run : Show what would be sent without actually writing}
|
||||
{--force : Index the document even when the artwork is not public/approved/published}
|
||||
{--no-cache-bump : Skip bumping the explore cache version after indexing}';
|
||||
|
||||
protected $description = 'Directly push a single artwork into Meilisearch, bypassing the queue.';
|
||||
|
||||
public function handle(MeilisearchClient $client): int
|
||||
{
|
||||
$artworkId = $this->resolveArtworkId();
|
||||
|
||||
if ($artworkId === null) {
|
||||
$this->error('An artwork ID is required.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$isDryRun = (bool) $this->option('dry-run');
|
||||
$forceIndex = (bool) $this->option('force');
|
||||
|
||||
$this->line(sprintf(
|
||||
'%sForce-indexing artwork #%d into Meilisearch%s…',
|
||||
$isDryRun ? '[DRY RUN] ' : '',
|
||||
$artworkId,
|
||||
$forceIndex ? ' (--force: eligibility check bypassed)' : '',
|
||||
));
|
||||
|
||||
// ── 1. Load artwork with all relations required for toSearchableArray() ──
|
||||
$artwork = Artwork::query()
|
||||
->with(['user', 'group', 'tags', 'categories.contentType', 'stats', 'awardStat'])
|
||||
->find($artworkId);
|
||||
|
||||
if ($artwork === null) {
|
||||
$this->error("Artwork #{$artworkId} was not found in the database.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->comment('Artwork');
|
||||
$this->line(sprintf(
|
||||
' id=%d title="%s" public=%s approved=%s published_at=%s',
|
||||
(int) $artwork->id,
|
||||
(string) ($artwork->title ?? ''),
|
||||
$artwork->is_public ? 'yes' : 'no',
|
||||
$artwork->is_approved ? 'yes' : 'no',
|
||||
$artwork->published_at?->toIso8601String() ?? 'null',
|
||||
));
|
||||
|
||||
// ── 2. Eligibility check ─────────────────────────────────────────────────
|
||||
$shouldBeIndexed = $artwork->is_public && $artwork->is_approved && $artwork->published_at !== null;
|
||||
|
||||
if (! $shouldBeIndexed && ! $forceIndex) {
|
||||
$this->warn(sprintf(
|
||||
'Artwork #%d is not eligible for the public index (is_public=%s, is_approved=%s, published_at=%s). ' .
|
||||
'Use --force to index it anyway, or fix the artwork status first.',
|
||||
$artworkId,
|
||||
$artwork->is_public ? 'true' : 'false',
|
||||
$artwork->is_approved ? 'true' : 'false',
|
||||
$artwork->published_at?->toIso8601String() ?? 'null',
|
||||
));
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! $shouldBeIndexed && $forceIndex) {
|
||||
$this->warn('Artwork is not normally eligible but --force was passed; indexing anyway.');
|
||||
}
|
||||
|
||||
// ── 3. Build the Meilisearch document ────────────────────────────────────
|
||||
$document = $artwork->toSearchableArray();
|
||||
|
||||
$this->comment('Generated document');
|
||||
$this->line(json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
$this->newLine();
|
||||
|
||||
// ── 4. Resolve index name ────────────────────────────────────────────────
|
||||
$indexName = $this->resolveIndexName($artwork);
|
||||
$this->line("Target index: {$indexName}");
|
||||
|
||||
if ($isDryRun) {
|
||||
$this->info('[DRY RUN] Document was NOT written to Meilisearch. Remove --dry-run to execute.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
// ── 5. Write directly to Meilisearch (no queue) ──────────────────────────
|
||||
try {
|
||||
$taskResult = $client->index($indexName)->addDocuments([$document]);
|
||||
$taskUid = $taskResult['taskUid'] ?? $taskResult['uid'] ?? 'n/a';
|
||||
$this->info(sprintf(
|
||||
'Document written to Meilisearch. Task uid: %s',
|
||||
is_scalar($taskUid) ? (string) $taskUid : json_encode($taskUid),
|
||||
));
|
||||
} catch (\Throwable $e) {
|
||||
$this->error('Meilisearch write failed: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// ── 6. Bump explore cache version ────────────────────────────────────────
|
||||
if (! $this->option('no-cache-bump')) {
|
||||
try {
|
||||
$newVersion = ((int) Cache::get('explore.cache.version', 1)) + 1;
|
||||
Cache::forever('explore.cache.version', $newVersion);
|
||||
$this->line("Explore cache version bumped to {$newVersion}.");
|
||||
} catch (\Throwable $e) {
|
||||
$this->warn('Could not bump explore cache version: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── 7. Summary ────────────────────────────────────────────────────────────
|
||||
$this->newLine();
|
||||
$this->info(sprintf(
|
||||
'Artwork #%d ("%s") has been pushed to index "%s" directly.',
|
||||
(int) $artwork->id,
|
||||
(string) ($artwork->title ?? ''),
|
||||
$indexName,
|
||||
));
|
||||
$this->line('The artwork should now appear on browse and search pages.');
|
||||
$this->line('If Meilisearch was still processing the task you can verify with:');
|
||||
$this->line(sprintf(' php artisan artworks:search-inspect %d', $artworkId));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function resolveArtworkId(): ?int
|
||||
{
|
||||
$argument = $this->argument('artwork_id');
|
||||
|
||||
if ($argument !== null && $argument !== '') {
|
||||
return max(1, (int) $argument);
|
||||
}
|
||||
|
||||
if (! $this->input->isInteractive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$answer = $this->ask('Artwork ID');
|
||||
|
||||
if ($answer === null || trim($answer) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return max(1, (int) $answer);
|
||||
}
|
||||
|
||||
private function resolveIndexName(Artwork $artwork): string
|
||||
{
|
||||
$override = trim((string) $this->option('index'));
|
||||
|
||||
if ($override !== '') {
|
||||
return $override;
|
||||
}
|
||||
|
||||
return $artwork->searchableAs();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user