145 lines
4.9 KiB
PHP
145 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\AiEditOutput;
|
|
use App\Models\AiEditRequest;
|
|
use App\Services\AiEditing\AiEditOutputStorageService;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class AiEditsBackfillStorageCommand extends Command
|
|
{
|
|
protected $signature = 'ai-edits:backfill-storage
|
|
{--request-id= : Restrict backfill to one AI edit request id}
|
|
{--limit=200 : Maximum outputs to process}
|
|
{--pretend : Dry run without writing changes}';
|
|
|
|
protected $description = 'Backfill local storage paths for AI outputs that only have provider URLs.';
|
|
|
|
public function __construct(private readonly AiEditOutputStorageService $outputStorage)
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
public function handle(): int
|
|
{
|
|
$limit = max(1, (int) $this->option('limit'));
|
|
$requestId = $this->normalizeRequestId($this->option('request-id'));
|
|
$pretend = (bool) $this->option('pretend');
|
|
|
|
$query = AiEditOutput::query()
|
|
->with('request')
|
|
->whereNotNull('provider_url')
|
|
->where(function (Builder $builder): void {
|
|
$builder
|
|
->whereNull('storage_path')
|
|
->orWhere('storage_path', '');
|
|
})
|
|
->orderBy('id');
|
|
|
|
if ($requestId !== null) {
|
|
$query->where('request_id', $requestId);
|
|
}
|
|
|
|
$candidateCount = (clone $query)->count();
|
|
$outputs = $query->limit($limit)->get();
|
|
|
|
if ($outputs->isEmpty()) {
|
|
$this->info('No AI outputs require storage backfill.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->line(sprintf(
|
|
'AI output backfill candidates: %d (processing up to %d).',
|
|
$candidateCount,
|
|
$limit
|
|
));
|
|
|
|
if ($pretend) {
|
|
$this->table(
|
|
['Output ID', 'Request ID', 'Provider URL'],
|
|
$outputs->map(static fn (AiEditOutput $output): array => [
|
|
(string) $output->id,
|
|
(string) $output->request_id,
|
|
(string) $output->provider_url,
|
|
])->all()
|
|
);
|
|
|
|
$this->info('Pretend mode enabled. No records were changed.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$processed = 0;
|
|
$stored = 0;
|
|
$failed = 0;
|
|
|
|
foreach ($outputs as $output) {
|
|
$processed++;
|
|
|
|
$request = $output->request;
|
|
if (! $request instanceof AiEditRequest) {
|
|
$failed++;
|
|
$this->warn(sprintf('Output %d skipped: missing request relation.', $output->id));
|
|
|
|
continue;
|
|
}
|
|
|
|
$persisted = $this->outputStorage->persist($request, [
|
|
'provider_url' => $output->provider_url,
|
|
'provider_asset_id' => $output->provider_asset_id,
|
|
'storage_disk' => $output->storage_disk,
|
|
'storage_path' => $output->storage_path,
|
|
'mime_type' => $output->mime_type,
|
|
'width' => $output->width,
|
|
'height' => $output->height,
|
|
'bytes' => $output->bytes,
|
|
'checksum' => $output->checksum,
|
|
'metadata' => $output->metadata,
|
|
]);
|
|
|
|
$output->forceFill([
|
|
'provider_url' => $persisted['provider_url'] ?? $output->provider_url,
|
|
'storage_disk' => $persisted['storage_disk'] ?? $output->storage_disk,
|
|
'storage_path' => $persisted['storage_path'] ?? $output->storage_path,
|
|
'mime_type' => $persisted['mime_type'] ?? $output->mime_type,
|
|
'width' => array_key_exists('width', $persisted) ? $persisted['width'] : $output->width,
|
|
'height' => array_key_exists('height', $persisted) ? $persisted['height'] : $output->height,
|
|
'bytes' => array_key_exists('bytes', $persisted) ? $persisted['bytes'] : $output->bytes,
|
|
'checksum' => $persisted['checksum'] ?? $output->checksum,
|
|
'metadata' => is_array($persisted['metadata'] ?? null) ? $persisted['metadata'] : $output->metadata,
|
|
])->save();
|
|
|
|
$storagePath = trim((string) ($output->storage_path ?? ''));
|
|
if ($storagePath !== '') {
|
|
$stored++;
|
|
} else {
|
|
$failed++;
|
|
$this->warn(sprintf('Output %d could not be persisted locally.', $output->id));
|
|
}
|
|
}
|
|
|
|
$this->info(sprintf(
|
|
'AI output backfill complete: processed=%d stored=%d failed=%d.',
|
|
$processed,
|
|
$stored,
|
|
$failed
|
|
));
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function normalizeRequestId(mixed $value): ?int
|
|
{
|
|
if (! is_numeric($value)) {
|
|
return null;
|
|
}
|
|
|
|
$requestId = (int) $value;
|
|
|
|
return $requestId > 0 ? $requestId : null;
|
|
}
|
|
}
|