feat: API contenuti taggati

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 17:18:30 +02:00
parent b5c0c107bc
commit eda4d73aac
7 changed files with 182 additions and 11 deletions
+31
View File
@@ -0,0 +1,31 @@
import type Database from 'better-sqlite3';
import { validateStyles } from './style-presets';
import { sanitizeInline } from './sanitize';
interface Body { value?: unknown; styles?: unknown }
export function applyContentUpdate(db: Database.Database, tag: string, body: Body, username: string):
{ ok: true } | { ok: false; status: number; error: string } {
const row = db.prepare('SELECT type FROM content_blocks WHERE tag = ?').get(tag) as { type: string } | undefined;
if (!row) return { ok: false, status: 404, error: 'Tag inesistente.' };
const sets: string[] = [];
const args: unknown[] = [];
if (body.value !== undefined) {
if (typeof body.value !== 'string') return { ok: false, status: 400, error: 'Valore non valido.' };
if (row.type === 'image') return { ok: false, status: 400, error: 'Le immagini si aggiornano via upload.' };
sets.push('value = ?');
args.push(row.type === 'html' ? sanitizeInline(body.value) : body.value);
}
if (body.styles !== undefined) {
const v = validateStyles(body.styles);
if (!v.ok) return { ok: false, status: 400, error: v.error };
sets.push('styles = ?');
args.push(JSON.stringify(v.styles));
}
if (!sets.length) return { ok: false, status: 400, error: 'Nessuna modifica.' };
sets.push(`updated_at = datetime('now')`, 'updated_by = ?');
args.push(username, tag);
db.prepare(`UPDATE content_blocks SET ${sets.join(', ')} WHERE tag = ?`).run(...args);
return { ok: true };
}
+16
View File
@@ -0,0 +1,16 @@
import { extname } from 'node:path';
export const ALLOWED: Record<string, string> = {
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.gif': 'image/gif',
};
export const MAX_BYTES = 5 * 1024 * 1024;
export function validateUploadFile(file: unknown):
| { ok: true; file: File; ext: string }
| { ok: false; error: string } {
if (!(file instanceof File)) return { ok: false, error: 'Nessun file.' };
const ext = extname(file.name).toLowerCase();
if (!ALLOWED[ext]) return { ok: false, error: 'Formato non consentito (jpg, png, webp, gif).' };
if (file.size > MAX_BYTES) return { ok: false, error: 'File troppo grande (max 5 MB).' };
return { ok: true, file, ext };
}