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 <noreply@anthropic.com>
This commit is contained in:
+22
-8
@@ -1349,13 +1349,13 @@ function saveHeroMeta() {
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function uploadHeroImage(input) {
|
async function uploadHeroImage(input) {
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = async (e) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = async () => {
|
||||||
const maxW = 1600;
|
const maxW = 1600;
|
||||||
let w = img.width, h = img.height;
|
let w = img.width, h = img.height;
|
||||||
if (w > maxW) { h = Math.round(h * maxW / w); w = maxW; }
|
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.width = w; canvas.height = h;
|
||||||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||||||
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
||||||
applyHeroImage(dataUrl);
|
|
||||||
heroMeta.posY = 50;
|
// Upload file to server (too large for NocoDB LongText)
|
||||||
applyHeroMeta();
|
try {
|
||||||
saveVisionSection('hero-image', dataUrl);
|
const res = await fetch('/api/hero-upload', {
|
||||||
saveHeroMeta();
|
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;
|
img.src = e.target.result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
// Auth gate — redirect to login page if not authenticated
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
if (isAuth(req)) {
|
if (isAuth(req)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user