import { randomUUID } from 'node:crypto'; import { mkdir, writeFile, unlink } from 'node:fs/promises'; import { join } from 'node:path'; import type { FastifyBaseLogger } from 'fastify'; export interface StorageService { store(data: Buffer, mimeType: string, prefix: string): Promise; delete(url: string): Promise; } const MIME_TO_EXT: Record = { 'image/jpeg': '.jpg', 'image/png': '.png', 'image/webp': '.webp', 'image/gif': '.gif', }; export function createLocalStorage( uploadDir: string, baseUrl: string, logger: Pick, ): StorageService { return { async store(data: Buffer, mimeType: string, prefix: string): Promise { const ext = MIME_TO_EXT[mimeType] ?? '.bin'; const filename = `${prefix}-${randomUUID()}${ext}`; const dir = join(uploadDir, prefix); await mkdir(dir, { recursive: true }); const filepath = join(dir, filename); await writeFile(filepath, data); logger.debug({ filepath, size: data.length }, 'File stored'); return `${baseUrl}/uploads/${prefix}/${filename}`; }, async delete(url: string): Promise { try { const uploadsIdx = url.indexOf('/uploads/'); if (uploadsIdx === -1) return; const relativePath = url.slice(uploadsIdx + '/uploads/'.length); const filepath = join(uploadDir, relativePath); await unlink(filepath); logger.debug({ filepath }, 'File deleted'); } catch { // Best-effort deletion } }, }; }