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; } }