Backfill avatars for existing users
linter / quality (pull_request) Successful in 1m31s
tests / ci (8.4) (pull_request) Successful in 1m51s

This commit is contained in:
2026-06-24 14:41:28 +02:00
parent 598f8631f9
commit f6ec164f27
3 changed files with 171 additions and 1 deletions
@@ -0,0 +1,109 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
return new class extends Migration
{
/**
* @var list<string>
*/
private const COLORS = [
'#F97316',
'#22C55E',
'#06B6D4',
'#EC4899',
'#8B5CF6',
'#EAB308',
'#EF4444',
'#14B8A6',
];
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('users')
->select(['id', 'name'])
->whereNull('avatar_path')
->orderBy('id')
->chunkById(100, function (Collection $users): void {
foreach ($users as $user) {
$color = self::COLORS[random_int(0, count(self::COLORS) - 1)];
$path = sprintf('avatars/generated/%d-%s.svg', $user->id, (string) Str::uuid());
$stored = Storage::disk('public')->put(
$path,
$this->svg($this->initials((string) $user->name), $color),
);
if (! $stored) {
throw new RuntimeException('Generated avatar could not be stored.');
}
DB::table('users')
->where('id', $user->id)
->update([
'avatar_path' => $path,
'avatar_color' => $color,
'avatar_is_generated' => true,
]);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::table('users')
->select(['avatar_path'])
->where('avatar_is_generated', true)
->whereNotNull('avatar_path')
->orderBy('id')
->chunkById(100, function (Collection $users): void {
foreach ($users as $user) {
Storage::disk('public')->delete($user->avatar_path);
}
});
DB::table('users')
->where('avatar_is_generated', true)
->update([
'avatar_path' => null,
'avatar_color' => null,
'avatar_is_generated' => false,
]);
}
private function initials(string $name): string
{
$parts = preg_split('/\s+/u', trim($name)) ?: [];
if ($parts === []) {
return '?';
}
$first = mb_substr($parts[0], 0, 1);
$last = count($parts) > 1 ? mb_substr($parts[count($parts) - 1], 0, 1) : '';
return mb_strtoupper($first.$last);
}
private function svg(string $initials, string $color): string
{
$escapedInitials = htmlspecialchars($initials, ENT_QUOTES | ENT_XML1, 'UTF-8');
return <<<SVG
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192" role="img" aria-label="Profile avatar">
<rect width="192" height="192" rx="42" fill="{$color}"/>
<text x="50%" y="52%" dominant-baseline="middle" text-anchor="middle" fill="#ffffff" font-family="Inter, ui-sans-serif, system-ui, sans-serif" font-size="72" font-weight="700">{$escapedInitials}</text>
</svg>
SVG;
}
};