83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import type { Actions, PageServerLoad } from './$types';
|
|
import { fail } from '@sveltejs/kit';
|
|
import { writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { getContent, resetImage, setImage } from '$lib/server/content';
|
|
import { DEFAULT_IMAGES } from '$lib/content/defaults';
|
|
|
|
const UPLOAD_DIR = join(process.cwd(), 'static', 'img', 'uploads');
|
|
const MAX_BYTES = 8 * 1024 * 1024; // 8 MB
|
|
const ALLOWED = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif', 'image/svg+xml']);
|
|
const LOCKED_SLOTS = new Set(['cima_logo']);
|
|
|
|
export const load: PageServerLoad = () => {
|
|
const content = getContent();
|
|
const images: typeof content.images = {};
|
|
for (const [id, img] of Object.entries(content.images)) {
|
|
if (LOCKED_SLOTS.has(id)) continue;
|
|
images[id] = img;
|
|
}
|
|
return { images };
|
|
};
|
|
|
|
function extFromType(type: string, fallbackName: string): string {
|
|
if (type === 'image/png') return 'png';
|
|
if (type === 'image/jpeg') return 'jpg';
|
|
if (type === 'image/webp') return 'webp';
|
|
if (type === 'image/gif') return 'gif';
|
|
if (type === 'image/svg+xml') return 'svg';
|
|
const m = /\.([a-zA-Z0-9]+)$/.exec(fallbackName);
|
|
return m ? m[1].toLowerCase() : 'bin';
|
|
}
|
|
|
|
export const actions: Actions = {
|
|
upload: async ({ request }) => {
|
|
if (!existsSync(UPLOAD_DIR)) mkdirSync(UPLOAD_DIR, { recursive: true });
|
|
const form = await request.formData();
|
|
const slot = String(form.get('slot') ?? '');
|
|
const alt = String(form.get('alt') ?? '').trim();
|
|
const file = form.get('file');
|
|
|
|
if (!slot || !(slot in DEFAULT_IMAGES) || LOCKED_SLOTS.has(slot)) return fail(400, { error: 'Slot immagine non valido.' });
|
|
if (!(file instanceof File) || !file.size) {
|
|
// alt-only update
|
|
if (alt) {
|
|
setImage(slot, { alt });
|
|
return { success: true, slot, altOnly: true };
|
|
}
|
|
return fail(400, { error: 'Nessun file caricato.' });
|
|
}
|
|
if (file.size > MAX_BYTES) return fail(400, { error: 'File troppo grande (max 8 MB).' });
|
|
if (file.type && !ALLOWED.has(file.type)) return fail(400, { error: `Tipo file non supportato: ${file.type}.` });
|
|
|
|
const buf = Buffer.from(await file.arrayBuffer());
|
|
const ext = extFromType(file.type, file.name);
|
|
const filename = `${slot}-${randomUUID().slice(0, 8)}.${ext}`;
|
|
const fullPath = join(UPLOAD_DIR, filename);
|
|
writeFileSync(fullPath, buf);
|
|
|
|
const publicPath = `/img/uploads/${filename}`;
|
|
const next = setImage(slot, { src: publicPath, alt: alt || undefined });
|
|
return { success: true, slot, src: next.src };
|
|
},
|
|
|
|
reset: async ({ request }) => {
|
|
const form = await request.formData();
|
|
const slot = String(form.get('slot') ?? '');
|
|
if (!slot || !(slot in DEFAULT_IMAGES) || LOCKED_SLOTS.has(slot)) return fail(400, { error: 'Slot non valido.' });
|
|
|
|
const current = getContent().images[slot];
|
|
if (current?.src?.startsWith('/img/uploads/')) {
|
|
const fullPath = join(process.cwd(), 'static', current.src);
|
|
try {
|
|
if (existsSync(fullPath)) unlinkSync(fullPath);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
resetImage(slot);
|
|
return { success: true, slot, reset: true };
|
|
}
|
|
};
|