diff --git a/public/editor.html b/public/editor.html index 54d5c74..104b5cf 100644 --- a/public/editor.html +++ b/public/editor.html @@ -30,6 +30,18 @@ .logo-row { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; } .logo-row input { flex: 1; background: #2a2a2a; border: 1px solid #444; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 12px; } .logo-row label { font-size: 12px; color: #aaa; white-space: nowrap; } + + .version-panel { background: #222; border: 1px solid #444; border-radius: 6px; padding: 16px; margin-bottom: 20px; } + .version-list { margin-top: 10px; max-height: 200px; overflow-y: auto; } + .version-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #333; font-size: 13px; } + .version-item:hover { background: #2a2a2a; } + .version-info { flex: 1; cursor: pointer; } + .version-name { font-weight: bold; color: #C9A84C; } + .version-date { font-size: 11px; color: #777; margin-top: 2px; } + .version-actions { display: flex; gap: 8px; } + .v-btn { padding: 4px 8px; font-size: 11px; } + .save-form { display: flex; gap: 8px; margin-top: 10px; } + .save-form input { flex: 1; background: #333; border: 1px solid #444; color: #eee; padding: 8px; border-radius: 4px; font-size: 13px; } @@ -43,6 +55,17 @@
+
+

Saved Versions

+
+ + +
+
+
Loading versions...
+
+
+

Handlebars Template (HTML)

@@ -80,9 +103,67 @@ async function saveTemplate() { const content = document.getElementById('template-editor').value; const res = await fetch('/api/admin/template',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content})}).then(r=>r.json()); - showStatus(res.ok ? 'Template saved! A .bak backup was created.' : 'Error: '+res.error, res.ok); + showStatus(res.ok ? 'Template deployed! A .bak backup was created.' : 'Error: '+res.error, res.ok); updatePreview(); } + + async function loadVersions() { + const versions = await fetch('/api/admin/versions').then(r=>r.json()).catch(()=>[]); + const list = document.getElementById('version-list'); + if (!versions.length) { + list.innerHTML = '
No saved versions yet.
'; + return; + } + list.innerHTML = versions.map(v => ` +
+
+
${v.name}
+
${new Date(v.updated_at).toLocaleString()}
+
+
+ +
+
+ `).join(''); + } + + async function saveVersion() { + const name = document.getElementById('v-name').value.trim(); + const content = document.getElementById('template-editor').value; + if (!name) return alert('Enter a version name.'); + + const res = await fetch('/api/admin/versions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, content }) + }).then(r=>r.json()); + + if (res.ok) { + showStatus('Version saved!', true); + document.getElementById('v-name').value = ''; + loadVersions(); + } else { + showStatus('Error: '+res.error, false); + } + } + + async function loadVersionData(id) { + const v = await fetch(`/api/admin/versions/${id}`).then(r=>r.json()); + document.getElementById('template-editor').value = v.content; + document.getElementById('v-name').value = v.name; + updatePreview(); + } + + async function deployVersion(id) { + if (!confirm('Set this version as the ACTIVE template?')) return; + const res = await fetch(`/api/admin/versions/${id}/deploy`, { method: 'POST' }).then(r=>r.json()); + if (res.ok) { + showStatus('Version deployed and active!', true); + } else { + showStatus('Error: '+res.error, false); + } + } + async function updatePreview() { const templateHtml = document.getElementById('template-editor').value; const userData = { @@ -102,6 +183,7 @@ setTimeout(()=>{ el.style.display='none'; }, 5000); } loadTemplate(); + loadVersions(); diff --git a/src/db/sqlite.js b/src/db/sqlite.js index bf53584..2d8529e 100644 --- a/src/db/sqlite.js +++ b/src/db/sqlite.js @@ -30,6 +30,12 @@ function initDb() { custom_html TEXT, updated_at TEXT ); + CREATE TABLE IF NOT EXISTS templates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + content TEXT NOT NULL, + updated_at TEXT NOT NULL + ); `); const existing = db.prepare("SELECT value FROM settings WHERE key='template_name'").get(); if (!existing) { diff --git a/src/db/templateDb.js b/src/db/templateDb.js new file mode 100644 index 0000000..8a27588 --- /dev/null +++ b/src/db/templateDb.js @@ -0,0 +1,34 @@ +const { getDb } = require('./sqlite'); + +function saveTemplate(name, content) { + const db = getDb(); + const timestamp = new Date().toISOString(); + + // Check if exists + const existing = db.prepare('SELECT id FROM templates WHERE name = ?').get(name); + + if (existing) { + return db.prepare('UPDATE templates SET content = ?, updated_at = ? WHERE id = ?') + .run(content, timestamp, existing.id); + } else { + return db.prepare('INSERT INTO templates (name, content, updated_at) VALUES (?, ?, ?)') + .run(name, content, timestamp); + } +} + +function getAllTemplateVersions() { + const db = getDb(); + return db.prepare('SELECT id, name, updated_at FROM templates ORDER BY updated_at DESC').all(); +} + +function getTemplateById(id) { + const db = getDb(); + return db.prepare('SELECT * FROM templates WHERE id = ?').get(id); +} + +function deleteTemplate(id) { + const db = getDb(); + return db.prepare('DELETE FROM templates WHERE id = ?').run(id); +} + +module.exports = { saveTemplate, getAllTemplateVersions, getTemplateById, deleteTemplate }; diff --git a/src/routes/admin.js b/src/routes/admin.js index c971c59..009d889 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -4,6 +4,11 @@ const fs = require('fs'); const path = require('path'); const { getRecentLogs } = require('../db/audit'); const { getAllUsers } = require('../services/googleAdmin'); +const { + getAllTemplateVersions, + getTemplateById, + saveTemplate +} = require('../db/templateDb'); router.get('/logs', (req, res) => { const logs = getRecentLogs(parseInt(req.query.limit) || 200); @@ -59,4 +64,51 @@ router.post('/preview', (req, res) => { } }); +router.get('/versions', (req, res) => { + try { + const versions = getAllTemplateVersions(); + res.json(versions); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +router.get('/versions/:id', (req, res) => { + try { + const version = getTemplateById(req.params.id); + if (!version) return res.status(404).json({ error: 'Not found' }); + res.json(version); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +router.post('/versions', (req, res) => { + const { name, content } = req.body; + if (!name || !content) return res.status(400).json({ error: 'name and content required' }); + try { + saveTemplate(name, content); + res.json({ ok: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +router.post('/versions/:id/deploy', (req, res) => { + try { + const version = getTemplateById(req.params.id); + if (!version) return res.status(404).json({ error: 'Not found' }); + + const templatePath = path.join(__dirname, '../../templates/default.hbs'); + // Create backup + if (fs.existsSync(templatePath)) { + fs.writeFileSync(templatePath + '.bak', fs.readFileSync(templatePath)); + } + fs.writeFileSync(templatePath, version.content); + res.json({ ok: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + module.exports = router;