feat: ship creator journey v2 and profile updates
This commit is contained in:
203
app/Console/Commands/AuditArtworkThumbnailsCommand.php
Normal file
203
app/Console/Commands/AuditArtworkThumbnailsCommand.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Services\Uploads\UploadStorageService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Throwable;
|
||||
|
||||
final class AuditArtworkThumbnailsCommand extends Command
|
||||
{
|
||||
protected $signature = 'artworks:audit-thumbnails
|
||||
{--id= : Audit only this artwork ID}
|
||||
{--limit= : Stop after processing this many artworks}
|
||||
{--chunk=200 : Number of artworks to scan per batch}
|
||||
{--variant=* : Specific thumbnail variants to check (defaults to all configured derivatives)}
|
||||
{--dry-run : Report missing thumbnails without updating the artworks table}';
|
||||
|
||||
protected $description = 'Check artwork thumbnails on the configured object storage disk and mark artworks with missing thumbnails.';
|
||||
|
||||
public function handle(UploadStorageService $storage): int
|
||||
{
|
||||
$artworkId = $this->option('id') !== null ? max(1, (int) $this->option('id')) : null;
|
||||
$limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null;
|
||||
$chunkSize = max(1, min((int) $this->option('chunk'), 1000));
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
$variants = $this->resolveVariants();
|
||||
if ($variants === []) {
|
||||
$this->error('No thumbnail variants are configured. Check uploads.derivatives.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! $dryRun && ! Schema::hasColumns('artworks', [
|
||||
'has_missing_thumbnails',
|
||||
'missing_thumbnail_variants_json',
|
||||
'thumbnails_checked_at',
|
||||
])) {
|
||||
$this->error('Artwork thumbnail audit columns are missing. Run the latest database migrations first.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$diskName = $storage->objectDiskName();
|
||||
$diskConfig = config("filesystems.disks.{$diskName}");
|
||||
if (! is_array($diskConfig)) {
|
||||
$this->error("Filesystem disk [{$diskName}] is not configured.");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$disk = Storage::disk($diskName);
|
||||
$this->info(sprintf(
|
||||
'Starting thumbnail audit. disk=%s variants=%s chunk=%d limit=%s dry_run=%s',
|
||||
$diskName,
|
||||
implode(',', $variants),
|
||||
$chunkSize,
|
||||
$limit !== null ? (string) $limit : 'all',
|
||||
$dryRun ? 'yes' : 'no',
|
||||
));
|
||||
|
||||
$query = Artwork::query()
|
||||
->select(['id', 'hash', 'thumb_ext'])
|
||||
->orderBy('id');
|
||||
|
||||
if ($artworkId !== null) {
|
||||
$query->whereKey($artworkId);
|
||||
}
|
||||
|
||||
$processed = 0;
|
||||
$healthy = 0;
|
||||
$missing = 0;
|
||||
$written = 0;
|
||||
$failed = 0;
|
||||
|
||||
$query->chunkById($chunkSize, function ($artworks) use ($storage, $disk, $variants, $limit, $dryRun, &$processed, &$healthy, &$missing, &$written, &$failed) {
|
||||
foreach ($artworks as $artwork) {
|
||||
if ($limit !== null && $processed >= $limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$missingVariants = $this->resolveMissingVariants($artwork, $variants, $storage, $disk);
|
||||
$hasMissing = $missingVariants !== [];
|
||||
|
||||
if ($hasMissing) {
|
||||
$missing++;
|
||||
$this->warn(sprintf(
|
||||
'Artwork %d missing thumbnails: %s',
|
||||
(int) $artwork->id,
|
||||
implode(',', $missingVariants),
|
||||
));
|
||||
} else {
|
||||
$healthy++;
|
||||
}
|
||||
|
||||
if (! $dryRun) {
|
||||
$this->persistAuditResult((int) $artwork->id, $hasMissing, $missingVariants);
|
||||
$written++;
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$failed++;
|
||||
$this->warn(sprintf('Artwork %d audit failed: %s', (int) $artwork->id, $exception->getMessage()));
|
||||
}
|
||||
|
||||
$processed++;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$this->info(sprintf(
|
||||
'Thumbnail audit complete. processed=%d healthy=%d missing=%d written=%d failed=%d',
|
||||
$processed,
|
||||
$healthy,
|
||||
$missing,
|
||||
$written,
|
||||
$failed,
|
||||
));
|
||||
|
||||
return $failed > 0 ? self::FAILURE : self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function resolveVariants(): array
|
||||
{
|
||||
$configured = array_keys((array) config('uploads.derivatives', []));
|
||||
$configured = array_values(array_filter(array_map(
|
||||
static fn ($variant): string => strtolower(trim((string) $variant)),
|
||||
$configured,
|
||||
)));
|
||||
|
||||
$requested = (array) $this->option('variant');
|
||||
if ($requested === []) {
|
||||
return $configured;
|
||||
}
|
||||
|
||||
$normalizedRequested = array_values(array_unique(array_filter(array_map(
|
||||
static fn ($variant): string => strtolower(trim((string) $variant)),
|
||||
$requested,
|
||||
))));
|
||||
|
||||
$invalid = array_values(array_diff($normalizedRequested, $configured));
|
||||
if ($invalid !== []) {
|
||||
$this->error('Unknown thumbnail variants: ' . implode(', ', $invalid));
|
||||
$this->line('Configured variants: ' . implode(', ', $configured));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return $normalizedRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $variants
|
||||
* @return list<string>
|
||||
*/
|
||||
private function resolveMissingVariants(Artwork $artwork, array $variants, UploadStorageService $storage, mixed $disk): array
|
||||
{
|
||||
$hash = strtolower((string) preg_replace('/[^a-z0-9]/', '', (string) ($artwork->hash ?? '')));
|
||||
$thumbExt = strtolower(ltrim((string) ($artwork->thumb_ext ?? ''), '.'));
|
||||
|
||||
if ($hash === '' || $thumbExt === '') {
|
||||
return $variants;
|
||||
}
|
||||
|
||||
$filename = $hash . '.' . $thumbExt;
|
||||
$missing = [];
|
||||
|
||||
foreach ($variants as $variant) {
|
||||
$objectPath = $storage->objectPathForVariant($variant, $hash, $filename);
|
||||
if (! $disk->exists($objectPath)) {
|
||||
$missing[] = $variant;
|
||||
}
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $missingVariants
|
||||
*/
|
||||
private function persistAuditResult(int $artworkId, bool $hasMissing, array $missingVariants): void
|
||||
{
|
||||
DB::table('artworks')
|
||||
->where('id', $artworkId)
|
||||
->update([
|
||||
'has_missing_thumbnails' => $hasMissing,
|
||||
'missing_thumbnail_variants_json' => $missingVariants === []
|
||||
? null
|
||||
: json_encode(array_values($missingVariants), JSON_UNESCAPED_SLASHES),
|
||||
'thumbnails_checked_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user