feat(03-01): add storage and thumbnail utilities

- Add storage.ts with directory management and file I/O
- Add thumbnails.ts with EXIF-aware thumbnail generation
- Thumbnails always output as JPEG with 80% quality
This commit is contained in:
Thomas Richter
2026-01-29 15:23:08 +01:00
parent f5b5034f07
commit 0987d16dc0
2 changed files with 84 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { mkdir, writeFile, unlink } from 'node:fs/promises';
import { join } from 'node:path';
export const UPLOAD_DIR = 'data/uploads';
export const ORIGINALS_DIR = 'data/uploads/originals';
export const THUMBNAILS_DIR = 'data/uploads/thumbnails';
/**
* Ensure upload directories exist
*/
export async function ensureDirectories(): Promise<void> {
await mkdir(ORIGINALS_DIR, { recursive: true });
await mkdir(THUMBNAILS_DIR, { recursive: true });
}
/**
* Save original image to filesystem
*/
export async function saveOriginal(id: string, ext: string, buffer: Buffer): Promise<void> {
const path = getOriginalPath(id, ext);
await writeFile(path, buffer);
}
/**
* Save thumbnail to filesystem (always jpg)
*/
export async function saveThumbnail(id: string, buffer: Buffer): Promise<void> {
const path = getThumbnailPath(id);
await writeFile(path, buffer);
}
/**
* Get path to original image
*/
export function getOriginalPath(id: string, ext: string): string {
return join(ORIGINALS_DIR, `${id}.${ext}`);
}
/**
* Get path to thumbnail (always jpg)
*/
export function getThumbnailPath(id: string): string {
return join(THUMBNAILS_DIR, `${id}.jpg`);
}
/**
* Delete both original and thumbnail files
* Does not throw if files don't exist
*/
export async function deleteImage(id: string, ext: string): Promise<void> {
try {
await unlink(getOriginalPath(id, ext));
} catch {
// File may not exist, ignore
}
try {
await unlink(getThumbnailPath(id));
} catch {
// File may not exist, ignore
}
}