fix(avatar): fix avatar cache busting by using avatarVersion (#1537)

* fix(avatar): fix avatar cache busting by using avatarVersion

Previously, avatar caching did not update the avatar when the remote image changed. This commit adds
logic to check if the avatar was modified remotely by comparing aremote last-modified timestamp with
a locally stored version (avatarVersion). If a change is detected, the cache is cleared, a new image
is fetched, and avatarVersionis updated. Otherwise, the cached image is retained.

* chore(db): add db migrations

* refactor: refactor imagehelpers util to where its used

* refactor: remove remnants from previous cache busting versions
This commit is contained in:
fallenbagel
2025-03-28 06:02:34 +08:00
committed by GitHub
parent 7438042757
commit 29034b350d
6 changed files with 232 additions and 13 deletions

View File

@@ -10,6 +10,7 @@ import { Permission } from '@server/lib/permissions';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { isAuthenticated } from '@server/middleware/auth';
import { checkAvatarChanged } from '@server/routes/avatarproxy';
import { ApiError } from '@server/types/error';
import { getHostname } from '@server/utils/getHostname';
import * as EmailValidator from 'email-validator';
@@ -216,6 +217,10 @@ authRoutes.post('/plex', async (req, res, next) => {
}
});
function getUserAvatarUrl(user: User): string {
return `/avatarproxy/${user.jellyfinUserId}?v=${user.avatarVersion}`;
}
authRoutes.post('/jellyfin', async (req, res, next) => {
const settings = getSettings();
const userRepository = getRepository(User);
@@ -343,12 +348,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: `/avatarproxy/${account.User.Id}`,
userType:
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY,
});
user.avatar = getUserAvatarUrl(user);
await userRepository.save(user);
} else {
@@ -375,7 +380,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
user.jellyfinDeviceId = deviceId;
user.jellyfinAuthToken = account.AccessToken;
user.permissions = Permission.ADMIN;
user.avatar = `/avatarproxy/${account.User.Id}`;
user.avatar = getUserAvatarUrl(user);
user.userType =
body.serverType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
@@ -422,7 +427,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinUsername: account.User.Name,
}
);
user.avatar = `/avatarproxy/${account.User.Id}`;
user.avatar = getUserAvatarUrl(user);
user.jellyfinUsername = account.User.Name;
if (user.username === account.User.Name) {
@@ -460,12 +465,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
permissions: settings.main.defaultPermissions,
avatar: `/avatarproxy/${account.User.Id}`,
userType:
settings.main.mediaServerType === MediaServerType.JELLYFIN
? UserType.JELLYFIN
: UserType.EMBY,
});
user.avatar = getUserAvatarUrl(user);
//initialize Jellyfin/Emby users with local login
const passedExplicitPassword = body.password && body.password.length > 0;
@@ -475,6 +480,26 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
await userRepository.save(user);
}
if (user && user.jellyfinUserId) {
try {
const { changed } = await checkAvatarChanged(user);
if (changed) {
user.avatar = getUserAvatarUrl(user);
await userRepository.save(user);
logger.debug('Avatar updated during login', {
userId: user.id,
jellyfinUserId: user.jellyfinUserId,
});
}
} catch (error) {
logger.error('Error handling avatar during login', {
label: 'Auth',
errorMessage: error.message,
});
}
}
// Set logged in session
if (req.session) {
req.session.userId = user?.id;