Repair: copy legacy joinDate into new user's created_at when creating users from legacy wallz

This commit is contained in:
2026-03-22 09:13:39 +01:00
parent e8b5edf5d2
commit 2608be7420
80 changed files with 3991 additions and 723 deletions

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Artwork;
use App\Models\Category;
use App\Services\Vision\ArtworkVisionImageUrl;
use App\Services\Vision\VectorGatewayClient;
use Illuminate\Console\Command;
final class SearchArtworkVectorsCommand extends Command
{
protected $signature = 'artworks:vectors-search
{artwork_id : Source artwork id}
{--limit=5 : Number of similar artworks to return}';
protected $description = 'Search similar artworks through the vector gateway using an artwork image URL';
public function handle(VectorGatewayClient $client, ArtworkVisionImageUrl $imageUrl): int
{
if (! $client->isConfigured()) {
$this->error('Vision vector gateway is not configured. Set VISION_VECTOR_GATEWAY_URL and VISION_VECTOR_GATEWAY_API_KEY.');
return self::FAILURE;
}
$artworkId = max(1, (int) $this->argument('artwork_id'));
$limit = max(1, min((int) $this->option('limit'), 100));
$artwork = Artwork::query()
->with(['categories' => fn ($categories) => $categories->with('contentType')->orderBy('sort_order')->orderBy('name')])
->find($artworkId);
if (! $artwork) {
$this->error("Artwork {$artworkId} was not found.");
return self::FAILURE;
}
$url = $imageUrl->fromArtwork($artwork);
if ($url === null) {
$this->error("Artwork {$artworkId} does not have a usable CDN image URL.");
return self::FAILURE;
}
try {
$matches = $client->searchByUrl($url, $limit + 1);
} catch (\Throwable $e) {
$this->error('Vector search failed: ' . $e->getMessage());
return self::FAILURE;
}
$ids = collect($matches)
->map(fn (array $match): int => (int) $match['id'])
->filter(fn (int $id): bool => $id > 0 && $id !== $artworkId)
->unique()
->take($limit)
->values()
->all();
if ($ids === []) {
$this->warn('No similar artworks were returned by the vector gateway.');
return self::SUCCESS;
}
$artworks = Artwork::query()
->with(['categories' => fn ($categories) => $categories->with('contentType')->orderBy('sort_order')->orderBy('name')])
->whereIn('id', $ids)
->public()
->published()
->get()
->keyBy('id');
$rows = [];
foreach ($matches as $match) {
$matchId = (int) ($match['id'] ?? 0);
if ($matchId <= 0 || $matchId === $artworkId) {
continue;
}
/** @var Artwork|null $matchedArtwork */
$matchedArtwork = $artworks->get($matchId);
if (! $matchedArtwork) {
continue;
}
$category = $this->primaryCategory($matchedArtwork);
$rows[] = [
'id' => $matchId,
'score' => number_format((float) ($match['score'] ?? 0.0), 4, '.', ''),
'title' => (string) $matchedArtwork->title,
'content_type' => (string) ($category?->contentType?->name ?? ''),
'category' => (string) ($category?->name ?? ''),
];
if (count($rows) >= $limit) {
break;
}
}
if ($rows === []) {
$this->warn('The vector gateway returned matches, but none resolved to public published artworks.');
return self::SUCCESS;
}
$this->table(['ID', 'Score', 'Title', 'Content Type', 'Category'], $rows);
return self::SUCCESS;
}
private function primaryCategory(Artwork $artwork): ?Category
{
/** @var Category|null $category */
$category = $artwork->categories->sortBy('sort_order')->first();
return $category;
}
}