feat(frontend): add thumbnailUrl helper with cache-bust param
Pure function returning /api/documents/{id}/thumbnail?v=<timestamp>
or null when thumbnailKey is missing. The encoded timestamp changes
whenever the backend regenerates a thumbnail (file replace),
invalidating browser caches despite the immutable Cache-Control.
Refs #307
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
37
frontend/src/lib/thumbnails.test.ts
Normal file
37
frontend/src/lib/thumbnails.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { thumbnailUrl } from './thumbnails';
|
||||
|
||||
describe('thumbnailUrl', () => {
|
||||
it('returns null when thumbnailKey is undefined', () => {
|
||||
expect(thumbnailUrl({ id: 'abc' })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns url without version param when thumbnailKey present but generatedAt missing', () => {
|
||||
expect(thumbnailUrl({ id: 'abc', thumbnailKey: 'thumbnails/abc.jpg' })).toBe(
|
||||
'/api/documents/abc/thumbnail'
|
||||
);
|
||||
});
|
||||
|
||||
it('appends encoded cache-bust param when generatedAt present', () => {
|
||||
const url = thumbnailUrl({
|
||||
id: 'abc',
|
||||
thumbnailKey: 'thumbnails/abc.jpg',
|
||||
thumbnailGeneratedAt: '2026-04-22T20:41:15.123456'
|
||||
});
|
||||
expect(url).toBe('/api/documents/abc/thumbnail?v=2026-04-22T20%3A41%3A15.123456');
|
||||
});
|
||||
|
||||
it('different generatedAt produces different URL — enables cache-bust on file replace', () => {
|
||||
const a = thumbnailUrl({
|
||||
id: 'x',
|
||||
thumbnailKey: 'thumbnails/x.jpg',
|
||||
thumbnailGeneratedAt: '2026-01-01T10:00:00'
|
||||
});
|
||||
const b = thumbnailUrl({
|
||||
id: 'x',
|
||||
thumbnailKey: 'thumbnails/x.jpg',
|
||||
thumbnailGeneratedAt: '2026-01-01T11:00:00'
|
||||
});
|
||||
expect(a).not.toBe(b);
|
||||
});
|
||||
});
|
||||
18
frontend/src/lib/thumbnails.ts
Normal file
18
frontend/src/lib/thumbnails.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
type ThumbnailDoc = {
|
||||
id: string;
|
||||
thumbnailKey?: string;
|
||||
thumbnailGeneratedAt?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the URL for a document thumbnail image, or returns null when the document
|
||||
* has no thumbnail yet. When `thumbnailGeneratedAt` is present it is appended as a
|
||||
* `?v=…` query param so the browser / proxy cache is invalidated whenever the file
|
||||
* is replaced (the backend regenerates thumbnails at the same S3 key on replace).
|
||||
*/
|
||||
export function thumbnailUrl(doc: ThumbnailDoc): string | null {
|
||||
if (!doc.thumbnailKey) return null;
|
||||
const base = `/api/documents/${doc.id}/thumbnail`;
|
||||
if (!doc.thumbnailGeneratedAt) return base;
|
||||
return `${base}?v=${encodeURIComponent(doc.thumbnailGeneratedAt)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user