From c518e017380e4603dfbe9947025a79b409cc977d Mon Sep 17 00:00:00 2001 From: Etienne Delvarre Date: Fri, 29 May 2026 11:49:43 +0200 Subject: [PATCH] Fix hero image persistence: file upload instead of NocoDB base64 NocoDB LongText field has ~95KB limit, hero image is ~456KB base64. New approach: POST /api/hero-upload saves image as file in /public/uploads/, NocoDB stores only the path. Docker volume configured for persistence. Co-Authored-By: Claude Opus 4.6 --- public/index.html | 30 ++++++++++++++++++++++-------- server.js | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/public/index.html b/public/index.html index b96c181..99d54b4 100644 --- a/public/index.html +++ b/public/index.html @@ -1349,13 +1349,13 @@ function saveHeroMeta() { }); })(); -function uploadHeroImage(input) { +async function uploadHeroImage(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); - reader.onload = (e) => { + reader.onload = async (e) => { const img = new Image(); - img.onload = () => { + img.onload = async () => { const maxW = 1600; let w = img.width, h = img.height; if (w > maxW) { h = Math.round(h * maxW / w); w = maxW; } @@ -1363,11 +1363,25 @@ function uploadHeroImage(input) { canvas.width = w; canvas.height = h; canvas.getContext('2d').drawImage(img, 0, 0, w, h); const dataUrl = canvas.toDataURL('image/jpeg', 0.85); - applyHeroImage(dataUrl); - heroMeta.posY = 50; - applyHeroMeta(); - saveVisionSection('hero-image', dataUrl); - saveHeroMeta(); + + // Upload file to server (too large for NocoDB LongText) + try { + const res = await fetch('/api/hero-upload', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dataUrl }), + }); + const data = await res.json(); + if (data.path) { + applyHeroImage(data.path); + heroMeta.posY = 50; + applyHeroMeta(); + saveVisionSection('hero-image', data.path); + saveHeroMeta(); + } + } catch (err) { + console.error('Hero upload error:', err); + } }; img.src = e.target.result; }; diff --git a/server.js b/server.js index 0a7cfdf..fa6dbe3 100644 --- a/server.js +++ b/server.js @@ -239,6 +239,28 @@ app.put('/api/contenu/:sceneId', requireAuth, async (req, res) => { } }); +// ── Hero image upload (file too large for NocoDB LongText) ── +const fs = require('fs'); + +app.post('/api/hero-upload', requireAuth, (req, res) => { + try { + const { dataUrl } = req.body; + if (!dataUrl || !dataUrl.startsWith('data:image/')) return res.status(400).json({ error: 'Invalid image' }); + const matches = dataUrl.match(/^data:image\/(\w+);base64,(.+)$/); + if (!matches) return res.status(400).json({ error: 'Invalid format' }); + const ext = matches[1] === 'jpeg' ? 'jpg' : matches[1]; + const buffer = Buffer.from(matches[2], 'base64'); + const uploadDir = path.join(__dirname, 'public', 'uploads'); + if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }); + const filename = `hero.${ext}`; + fs.writeFileSync(path.join(uploadDir, filename), buffer); + const publicPath = `/uploads/${filename}?t=${Date.now()}`; + res.json({ path: publicPath }); + } catch (e) { + res.status(500).json({ error: e.message }); + } +}); + // Auth gate — redirect to login page if not authenticated app.get('/', (req, res) => { if (isAuth(req)) {