fix: album thumbnail refresh

This commit is contained in:
Jason Rasmussen
2026-02-04 16:31:32 -05:00
parent e9f8521a50
commit 6e69052478
5 changed files with 121 additions and 5 deletions

View File

@@ -2221,6 +2221,71 @@
"x-immich-state": "Stable"
}
},
"/albums/{id}/thumbnail": {
"get": {
"description": "Virtual route that redirects to the thumbnail of the album cover asset.",
"operationId": "getAlbumThumbnailRedirect",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "slug",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"summary": "Redirect to album thumbnail",
"tags": [
"Albums"
],
"x-immich-history": [
{
"version": "v2.6.0",
"state": "Added"
},
{
"version": "v2.6.0",
"state": "Beta"
}
],
"x-immich-permission": "album.read",
"x-immich-state": "Beta"
}
},
"/albums/{id}/user/{userId}": {
"delete": {
"description": "Remove a user from an album. Use an ID of \"me\" to leave a shared album.",

View File

@@ -1,4 +1,17 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
Put,
Query,
Redirect,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import {
@@ -73,6 +86,19 @@ export class AlbumController {
return this.service.get(auth, id, dto);
}
@Authenticated({ permission: Permission.AlbumRead, sharedLink: true })
@Get(':id/thumbnail')
@Redirect()
@Endpoint({
summary: 'Redirect to album thumbnail',
description: 'Virtual route that redirects to the thumbnail of the album cover asset.',
history: new HistoryBuilder().added('v2.6.0').beta('v2.6.0'),
})
async getAlbumThumbnailRedirect(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
const url = await this.service.getThumbnailRedirectUrl(auth, id);
return { url, status: 307 };
}
@Patch(':id')
@Authenticated({ permission: Permission.AlbumUpdate })
@Endpoint({

View File

@@ -87,6 +87,16 @@ export class AlbumRepository {
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async getForThumbnailRedirect(id: string) {
return this.db
.selectFrom('asset')
.innerJoin('album', 'album.albumThumbnailAssetId', 'asset.id')
.where('album.id', '=', id)
.select(['asset.id', 'asset.thumbhash'])
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async getByAssetId(ownerId: string, assetId: string) {
return this.db

View File

@@ -21,6 +21,7 @@ import { Permission } from 'src/enum';
import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository';
import { BaseService } from 'src/services/base.service';
import { addAssets, removeAssets } from 'src/utils/asset.util';
import { hexOrBufferToBase64 } from 'src/utils/bytes';
import { getPreferences } from 'src/utils/preferences';
@Injectable()
@@ -93,6 +94,23 @@ export class AlbumService extends BaseService {
};
}
async getThumbnailRedirectUrl(auth: AuthDto, id: string) {
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
const asset = await this.albumRepository.getForThumbnailRedirect(id);
if (!asset) {
throw new BadRequestException('Album has no thumbnail');
}
const params = new URLSearchParams();
params.append('edited', 'true');
if (asset.thumbhash) {
params.append('c', hexOrBufferToBase64(asset.thumbhash));
}
return `/api/assets/${asset.id}/thumbnail?${params.toString()}`;
}
async create(auth: AuthDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> {
const albumUsers = dto.albumUsers || [];

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte';
import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte';
import { getAssetMediaUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
@@ -14,9 +13,7 @@
let { album, preload = false, class: className = '' }: Props = $props();
let alt = $derived(album.albumName || $t('unnamed_album'));
let thumbnailUrl = $derived(
album.albumThumbnailAssetId ? getAssetMediaUrl({ id: album.albumThumbnailAssetId }) : null,
);
let thumbnailUrl = $derived(album.albumThumbnailAssetId ? `/api/albums/${album.id}/thumbnail` : null);
</script>
{#if thumbnailUrl}