login update
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\User;
|
||||
use App\Models\UserProfile;
|
||||
use Intervention\Image\ImageManagerStatic as Image;
|
||||
@@ -39,6 +40,7 @@ class AvatarsMigrate extends Command
|
||||
protected $allowed = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
@@ -47,7 +49,7 @@ class AvatarsMigrate extends Command
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
protected $sizes = [32, 64, 128, 256, 512];
|
||||
protected $sizes = [32, 40, 64, 128, 256, 512];
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
@@ -56,6 +58,7 @@ class AvatarsMigrate extends Command
|
||||
$removeLegacy = $this->option('remove-legacy');
|
||||
$legacyPath = base_path($this->option('path'));
|
||||
$userId = $this->option('user-id') ? (int) $this->option('user-id') : null;
|
||||
$verbose = $this->output->isVerbose();
|
||||
|
||||
$this->info('Starting avatar migration' . ($dry ? ' (dry-run)' : '') . ($userId ? " for user={$userId}" : ''));
|
||||
|
||||
@@ -72,7 +75,7 @@ class AvatarsMigrate extends Command
|
||||
$query->where('id', $userId);
|
||||
}
|
||||
|
||||
$query->chunk(100, function ($users) use ($dry, $force, $removeLegacy, $legacyPath, &$bar, $useIntervention) {
|
||||
$query->chunk(100, function ($users) use ($dry, $force, $removeLegacy, $legacyPath, &$bar, $useIntervention, $verbose) {
|
||||
foreach ($users as $user) {
|
||||
/** @var UserProfile|null $profile */
|
||||
$profile = $user->profile;
|
||||
@@ -87,10 +90,13 @@ class AvatarsMigrate extends Command
|
||||
continue;
|
||||
}
|
||||
|
||||
$source = $this->findLegacyFile($profile, $user->id, $legacyPath);
|
||||
$source = $this->findLegacyFile($profile, $user->id, $legacyPath, 'legacy');
|
||||
|
||||
//dd($source);
|
||||
if (!$source) {
|
||||
$this->line("[noop] user={$user->id} no legacy file found");
|
||||
if ($verbose) {
|
||||
$this->line("[noop] user={$user->id} no legacy file found");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -123,14 +129,19 @@ class AvatarsMigrate extends Command
|
||||
$contentPart = substr(sha1($originalBlob), 0, 12);
|
||||
$hash = sprintf('%s_%s', $idPart, $contentPart);
|
||||
|
||||
// Precompute storage dir for dry-run and real run
|
||||
$hashPrefix1 = substr($hash, 0, 2);
|
||||
$hashPrefix2 = substr($hash, 2, 2);
|
||||
$dir = "avatars/{$hashPrefix1}/{$hashPrefix2}/{$hash}";
|
||||
|
||||
// CDN base for public URLs
|
||||
$cdnBase = rtrim((string) config('cdn.avatar_url', 'https://files.skinbase.org'), '/');
|
||||
|
||||
if ($dry) {
|
||||
$this->line("[dry] user={$user->id} would write avatars for hash={$hash}");
|
||||
$absPathDry = Storage::disk('public')->path("{$dir}/original.webp");
|
||||
$publicUrlDry = sprintf('%s/%s/original.webp?v=%s', $cdnBase, $dir, $hash);
|
||||
$this->line("[dry] user={$user->id} would write avatars for hash={$hash} path={$absPathDry} url={$publicUrlDry}");
|
||||
} else {
|
||||
// Use hash-based directory structure: avatars/ab/cd/{hash}/
|
||||
$hashPrefix1 = substr($hash, 0, 2);
|
||||
$hashPrefix2 = substr($hash, 2, 2);
|
||||
$dir = "avatars/{$hashPrefix1}/{$hashPrefix2}/{$hash}";
|
||||
Storage::disk('public')->makeDirectory($dir);
|
||||
|
||||
// Save original.webp
|
||||
Storage::disk('public')->put("{$dir}/original.webp", $originalBlob);
|
||||
@@ -155,7 +166,9 @@ class AvatarsMigrate extends Command
|
||||
$profile->avatar_updated_at = Carbon::now();
|
||||
$profile->save();
|
||||
|
||||
$this->line("[ok] user={$user->id} migrated hash={$hash}");
|
||||
$absPath = Storage::disk('public')->path("{$dir}/original.webp");
|
||||
$publicUrl = sprintf('%s/%s/original.webp?v=%s', $cdnBase, $dir, $hash);
|
||||
$this->line("[ok] user={$user->id} migrated hash={$hash} path={$absPath} url={$publicUrl}");
|
||||
|
||||
if ($removeLegacy && !empty($profile->avatar_legacy)) {
|
||||
$legacyFile = base_path("public/files/usericons/{$profile->avatar_legacy}");
|
||||
@@ -185,8 +198,19 @@ class AvatarsMigrate extends Command
|
||||
* @param string $legacyBase
|
||||
* @return string|null
|
||||
*/
|
||||
protected function findLegacyFile(UserProfile $profile, int $userId, string $legacyBase): ?string
|
||||
protected function findLegacyFile(UserProfile $profile, int $userId, string $legacyBase, ?string $legacyConnection = null): ?string
|
||||
{
|
||||
|
||||
$avatar = DB::connection('legacy')->table('users')->where('user_id', $userId)->value('icon');
|
||||
|
||||
if (!empty($profile->avatar_legacy)) {
|
||||
$p = $legacyBase . DIRECTORY_SEPARATOR . $avatar;
|
||||
if (file_exists($p)) {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 1) If profile->avatar_legacy looks like a filename, try it
|
||||
if (!empty($profile->avatar_legacy)) {
|
||||
$p = $legacyBase . DIRECTORY_SEPARATOR . $profile->avatar_legacy;
|
||||
@@ -212,6 +236,34 @@ class AvatarsMigrate extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Fallback: try legacy database connection (connection name 'legacy')
|
||||
// If a legacy DB connection is configured, query `users.icon` for avatar filename.
|
||||
try {
|
||||
$conn = $legacyConnection ?: (config('database.connections.legacy') ? 'legacy' : null);
|
||||
if ($conn) {
|
||||
$icon = DB::connection($conn)->table('users')->where('id', $userId)->value('icon');
|
||||
if (!empty($icon)) {
|
||||
// If icon looks like an absolute path, use it directly; otherwise resolve under legacy base path
|
||||
$p = $icon;
|
||||
if (!file_exists($p)) {
|
||||
$p = $legacyBase . DIRECTORY_SEPARATOR . ltrim($icon, '\/');
|
||||
}
|
||||
|
||||
if (file_exists($p)) {
|
||||
if ($this->output->isVerbose()) {
|
||||
$this->line("[legacy-db] user={$userId} icon={$icon} resolved={$p}");
|
||||
}
|
||||
return $p;
|
||||
}
|
||||
if ($this->output->isVerbose()) {
|
||||
$this->line("[legacy-db] user={$userId} icon={$icon} not found at resolved path {$p}");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Non-fatal: just skip legacy DB if query fails or connection missing
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -308,6 +360,53 @@ class AvatarsMigrate extends Command
|
||||
return imagecreatefromwebp($path);
|
||||
}
|
||||
return false;
|
||||
case 'image/gif':
|
||||
if (function_exists('imagecreatefromgif')) {
|
||||
$res = imagecreatefromgif($path);
|
||||
if (!$res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure returned resource is truecolor (WebP requires truecolor)
|
||||
if (!imageistruecolor($res)) {
|
||||
$w = imagesx($res);
|
||||
$h = imagesy($res);
|
||||
$true = imagecreatetruecolor($w, $h);
|
||||
|
||||
// Preserve transparency where possible
|
||||
imagealphablending($true, false);
|
||||
imagesavealpha($true, true);
|
||||
|
||||
// Fill with fully transparent color
|
||||
$transparent = imagecolorallocatealpha($true, 0, 0, 0, 127);
|
||||
imagefilledrectangle($true, 0, 0, $w, $h, $transparent);
|
||||
|
||||
// If the source has an indexed transparent color, try to preserve it
|
||||
$transIndex = imagecolortransparent($res);
|
||||
if ($transIndex >= 0) {
|
||||
try {
|
||||
$colorTotal = imagecolorstotal($res);
|
||||
if ($transIndex >= 0 && $transIndex < $colorTotal) {
|
||||
$colors = imagecolorsforindex($res, $transIndex);
|
||||
if (is_array($colors)) {
|
||||
$alphaColor = imagecolorallocatealpha($true, $colors['red'], $colors['green'], $colors['blue'], 127);
|
||||
imagefilledrectangle($true, 0, 0, $w, $h, $alphaColor);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Non-fatal: skip preserving indexed transparent color
|
||||
}
|
||||
}
|
||||
|
||||
// Copy pixels
|
||||
imagecopy($true, $res, 0, 0, 0, 0, $w, $h);
|
||||
imagedestroy($res);
|
||||
return $true;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user