import { Hono } from 'hono'; import { mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; import sharp from 'sharp'; // Bild-Upload → static/images/. Raster-Bilder werden zu WebP konvertiert // (kleiner, web-optimiert), auf max. 2000px begrenzt, EXIF-Rotation korrigiert. // SVG/GIF bleiben unangetastet (Vektor/Animation erhalten). const SITE_DIR = process.env.SITE_DIR || '/site'; const PASSTHROUGH = new Set(['.svg', '.gif']); const upload = new Hono(); upload.post('/', async (c) => { const body = await c.req.parseBody(); const file = body['file']; if (!file || typeof file === 'string') return c.json({ error: 'Keine Datei' }, 400); const dir = path.join(SITE_DIR, 'static', 'images'); await mkdir(dir, { recursive: true }); const buffer = Buffer.from(await file.arrayBuffer()); const ext = path.extname(file.name || '').toLowerCase(); const base = `${safeBase(file.name)}-${uid()}`; let outName, outBuf; if (PASSTHROUGH.has(ext)) { outName = `${base}${ext}`; outBuf = buffer; } else { outName = `${base}.webp`; outBuf = await sharp(buffer) .rotate() .resize({ width: 2000, withoutEnlargement: true }) .webp({ quality: 82 }) .toBuffer(); } await writeFile(path.join(dir, outName), outBuf); return c.json({ url: `/images/${outName}` }); }); // Sicherer Basisname ohne Endung. function safeBase(raw) { const base = path.basename(String(raw || 'bild')).replace(/\.[^.]+$/, ''); const cleaned = base.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); return cleaned || 'bild'; } // Kurze eindeutige Endung, damit gleichnamige Uploads nicht kollidieren. function uid() { return Date.now().toString(36).slice(-4) + Math.random().toString(36).slice(2, 5); } export default upload;