feat(F-059): completed feature

This commit is contained in:
chattie
2026-08-19 15:32:56 +02:00
parent 4cdb5fb487
commit 72ba84456c
32 changed files with 369 additions and 57 deletions

View File

@@ -17,6 +17,14 @@ import path from 'node:path';
export const dynamic = 'force-dynamic';
const THUMB_SIZES: Readonly<Record<string, number>> = { '40': 40, '200': 200 };
/**
* Maximum height ratio (h/w) for cached thumbnails. The thumbnail is scaled
* to fit inside a `width × width * MAX_HEIGHT_RATIO` bounding box without
* cropping, so a non-square source produces a non-square thumbnail that
* preserves the original aspect ratio. The frontend renders the result with
* `object-contain`.
*/
const MAX_HEIGHT_RATIO = 1.4;
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
const CONTENT_TYPE_BY_EXTENSION: Readonly<Record<string, string>> = {
'.jpg': 'image/jpeg',
@@ -55,7 +63,10 @@ async function findOriginal(filename: string): Promise<{ root: string; buffer: B
async function buildThumbnail(source: Buffer, width: number): Promise<Buffer | null> {
try {
const { default: sharp } = await import('sharp');
return await sharp(source).resize({ width, withoutEnlargement: true }).toBuffer();
const maxHeight = Math.round(width * MAX_HEIGHT_RATIO);
return await sharp(source)
.resize({ width, height: maxHeight, fit: 'inside', withoutEnlargement: true })
.toBuffer();
} catch {
return null;
}