diff --git a/scripts/cms-versions.js b/scripts/cms-versions.js index 8097b66..ff40d45 100644 --- a/scripts/cms-versions.js +++ b/scripts/cms-versions.js @@ -1,8 +1,10 @@ // Version history helpers for the CMS: listing automatic backups from -// .content-backups, safe backup-name validation and diff assembly. +// .content-backups, safe backup-name validation, diff assembly and the +// /versions + /restore route handlers. const fs = require('fs'); const path = require('path'); const { renderDiffHtml } = require('./cms-diff'); +const { backupAndWriteAtomically: backupAndWrite } = require('./cms-core'); // Backup files are named `..json` const BACKUP_NAME_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/; @@ -56,4 +58,55 @@ function buildVersionDiff(backupDir, currentFilePath, fileKey, backupName) { }; } -module.exports = { safeBackupName, listVersions, readBackupContent, buildVersionDiff, formatBackupTimestamp }; +// WHY: route handling extracted here so content-editor.js stays under the +// 400-line limit. Returns true when the request was handled. +function handleVersionRoutes({ req, res, u, activeFile, backupDir, currentFile, validate, writeAudit, csrfOk, clientAddress, user, versionsPage }) { + if (req.method === 'GET' && u.pathname === '/versions') { + const versions = listVersions(backupDir, activeFile); + let diff = null; + const showRaw = u.searchParams.get('show'); + if (showRaw) { + const safe = safeBackupName(activeFile, showRaw); + if (safe) { + try { + diff = buildVersionDiff(backupDir, currentFile, activeFile, safe); + } catch { /* unreadable backup: render list only */ } + } + } + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); + res.end(versionsPage(activeFile, diff)); + return true; + } + + if (req.method === 'POST' && u.pathname === '/restore') { + const backup = safeBackupName(activeFile, u.searchParams.get('backup') || ''); + if (!backup) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: false, error: 'Érvénytelen mentésnév.' })); + return true; + } + try { + const data = JSON.parse(readBackupContent(backupDir, backup)); + const validation = validate(activeFile, data); + if (!validation.ok) { + writeAudit('version_restored', { clientAddress, user, file: activeFile, backup, result: 'validation_failed' }); + res.writeHead(422, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: false, error: 'A mentés nem felel meg a sémának: ' + validation.errors.join('; ') })); + return true; + } + backupAndWrite(currentFile, data, backupDir); + writeAudit('version_restored', { clientAddress, user, file: activeFile, backup, result: 'ok' }); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: true })); + } catch (e) { + writeAudit('version_restored', { clientAddress, user, file: activeFile, backup, result: 'error' }); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: false, error: e.message })); + } + return true; + } + + return false; +} + +module.exports = { safeBackupName, listVersions, readBackupContent, buildVersionDiff, formatBackupTimestamp, handleVersionRoutes };