- Infinite loop carousels for Similar Artworks & Trending rails
- Mouse wheel horizontal scrolling on both carousels
- Author avatar shown on hover in RailCard (similar + trending)
- Removed "View" badge from RailCard hover overlay
- Added `id` to Meilisearch filterable attributes
- Auto-prepend Scout prefix in meilisearch:configure-index command
- Added author name + avatar to Similar Artworks API response
- Added avatar_url to ArtworkListResource author object
- Added direct /art/{id}/{slug} URL to ArtworkListResource
- Fixed race condition: Similar Artworks no longer briefly shows trending items
- Fixed user_profiles eager load (user_id primary key, not id)
- Bumped /api/art/{id}/similar rate limit to 300/min
- Removed decorative heart icons from tag pills
- Moved ReactionBar under artwork description
58 lines
1.5 KiB
PHP
58 lines
1.5 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class AvatarUrl
|
|
{
|
|
private static array $hashCache = [];
|
|
|
|
public static function forUser(int $userId, ?string $hash = null, int $size = 128): string
|
|
{
|
|
if ($userId <= 0) {
|
|
return self::default();
|
|
}
|
|
|
|
$avatarHash = $hash ?: self::resolveHash($userId);
|
|
|
|
if (!$avatarHash) {
|
|
return self::default();
|
|
}
|
|
|
|
$base = rtrim((string) config('cdn.avatar_url', 'https://files.skinbase.org'), '/');
|
|
|
|
// Use hash-based path: avatars/ab/cd/{hash}/{size}.webp?v={hash}
|
|
$p1 = substr($avatarHash, 0, 2);
|
|
$p2 = substr($avatarHash, 2, 2);
|
|
|
|
return sprintf('%s/avatars/%s/%s/%s/%d.webp?v=%s', $base, $p1, $p2, $avatarHash, $size, $avatarHash);
|
|
}
|
|
|
|
public static function default(): string
|
|
{
|
|
$base = rtrim((string) config('cdn.avatar_url', 'https://files.skinbase.org'), '/');
|
|
|
|
return sprintf('%s/default/avatar_default.webp', $base);
|
|
}
|
|
|
|
private static function resolveHash(int $userId): ?string
|
|
{
|
|
if (array_key_exists($userId, self::$hashCache)) {
|
|
return self::$hashCache[$userId];
|
|
}
|
|
|
|
try {
|
|
$value = DB::table('user_profiles')
|
|
->where('user_id', $userId)
|
|
->value('avatar_hash');
|
|
} catch (\Throwable $e) {
|
|
$value = null;
|
|
}
|
|
|
|
self::$hashCache[$userId] = $value ? (string) $value : null;
|
|
|
|
return self::$hashCache[$userId];
|
|
}
|
|
}
|