Files
SkinbaseNova/app/Console/Commands/AvatarsMigrate.php
2026-02-17 17:14:43 +01:00

126 lines
3.9 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\AvatarService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class AvatarsMigrate extends Command
{
protected $signature = 'avatars:migrate {--force : Reprocess avatars even when avatar_hash is already present} {--limit=0 : Limit number of users processed}';
protected $description = 'Migrate legacy avatars into CDN-ready WebP variants and avatar_hash metadata';
public function __construct(private readonly AvatarService $service)
{
parent::__construct();
}
public function handle(): int
{
$force = (bool) $this->option('force');
$limit = max(0, (int) $this->option('limit'));
$this->info('Starting avatar migration...');
$rows = DB::table('user_profiles as p')
->leftJoin('users as u', 'u.id', '=', 'p.user_id')
->select([
'p.user_id',
'p.avatar_hash',
'p.avatar_legacy',
'u.icon as user_icon',
])
->when(!$force, fn ($query) => $query->whereNull('p.avatar_hash'))
->where(function ($query) {
$query->whereNotNull('p.avatar_legacy')
->orWhereNotNull('u.icon');
})
->orderBy('p.user_id')
->when($limit > 0, fn ($query) => $query->limit($limit))
->get();
if ($rows->isEmpty()) {
$this->info('No avatars require migration.');
return self::SUCCESS;
}
$migrated = 0;
$skipped = 0;
$failed = 0;
foreach ($rows as $row) {
$userId = (int) $row->user_id;
$legacyName = $this->normalizeLegacyName($row->avatar_legacy ?: $row->user_icon);
if ($legacyName === null) {
$skipped++;
continue;
}
$path = $this->locateLegacyAvatarPath($userId, $legacyName);
if ($path === null) {
$failed++;
$this->warn("User {$userId}: legacy avatar not found ({$legacyName})");
continue;
}
try {
$hash = $this->service->storeFromLegacyFile($userId, $path);
if (!$hash) {
$failed++;
$this->warn("User {$userId}: unable to process legacy avatar ({$legacyName})");
continue;
}
$migrated++;
$this->line("User {$userId}: migrated ({$hash})");
} catch (\Throwable $e) {
$failed++;
$this->warn("User {$userId}: migration failed ({$e->getMessage()})");
}
}
$this->info("Avatar migration complete. Migrated={$migrated}, Skipped={$skipped}, Failed={$failed}");
return $failed > 0 ? self::FAILURE : self::SUCCESS;
}
private function normalizeLegacyName(?string $value): ?string
{
if (!$value) {
return null;
}
$trimmed = trim($value);
if ($trimmed === '') {
return null;
}
return basename(urldecode($trimmed));
}
private function locateLegacyAvatarPath(int $userId, string $legacyName): ?string
{
$candidates = [
public_path('avatar/' . $legacyName),
public_path('avatar/' . $userId . '/' . $legacyName),
public_path('user-picture/' . $legacyName),
storage_path('app/public/avatar/' . $legacyName),
storage_path('app/public/avatar/' . $userId . '/' . $legacyName),
storage_path('app/public/user-picture/' . $legacyName),
base_path('oldSite/www/files/usericons/' . $legacyName),
];
foreach ($candidates as $candidate) {
if (is_string($candidate) && is_file($candidate) && is_readable($candidate)) {
return $candidate;
}
}
return null;
}
}