Monorepo with Express + SQLite backend and Vite + React frontend. Features: item CRUD, file uploads with thumbnails, soft delete, item duplication with file copying, autocomplete inputs, stats dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
import sharp from 'sharp';
|
|
import { join, parse } from 'path';
|
|
import { UPLOADS_DIR } from './middleware/upload.js';
|
|
|
|
interface VariantSpec {
|
|
suffix: string;
|
|
maxDimension: number;
|
|
}
|
|
|
|
const VARIANTS: VariantSpec[] = [
|
|
{ suffix: '_thumb', maxDimension: 300 },
|
|
{ suffix: '_medium', maxDimension: 768 },
|
|
{ suffix: '_large', maxDimension: 1920 },
|
|
];
|
|
|
|
const RESIZABLE_MIMES = new Set([
|
|
'image/jpeg', 'image/png', 'image/webp', 'image/heic',
|
|
]);
|
|
|
|
export function isResizable(mimeType: string): boolean {
|
|
return RESIZABLE_MIMES.has(mimeType);
|
|
}
|
|
|
|
export async function generateVariants(storedName: string): Promise<void> {
|
|
const { name, ext } = parse(storedName);
|
|
const sourcePath = join(UPLOADS_DIR, storedName);
|
|
|
|
for (const variant of VARIANTS) {
|
|
const outPath = join(UPLOADS_DIR, `${name}${variant.suffix}${ext}`);
|
|
await sharp(sourcePath)
|
|
.resize(variant.maxDimension, variant.maxDimension, {
|
|
fit: 'inside',
|
|
withoutEnlargement: true,
|
|
})
|
|
.toFile(outPath);
|
|
}
|
|
}
|
|
|
|
export function getVariantFilenames(storedName: string): string[] {
|
|
const { name, ext } = parse(storedName);
|
|
return VARIANTS.map(v => `${name}${v.suffix}${ext}`);
|
|
}
|