From 23d39dec65deeeb8aaff814cab6d2d590f6b109f Mon Sep 17 00:00:00 2001 From: Daniel Onyejesi Date: Tue, 24 Mar 2026 02:28:43 -0400 Subject: [PATCH] Fix WebDAV selection, pdf-parse, prompt(), CSP, security issues 1/2/4/5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pdf-parse (was v2, broken API): - Downgraded to v1.1.1 — default export is a function again - pdfParse is not a function error fixed WebDAV file selection (style.display bug, same cascade issue): - lh-ai-webdav-selected had inline style="display:flex" always visible - selectWebdavFile() / deselectWebdavFile() now use element.style.display - On select: browser div hides, selected indicator shows with file name + X - On deselect (X): indicator hides, browser shows again Topic context for Upload and Nextcloud tabs: - Both tabs now have optional "Topic / context" field - Sent to backend as 'topic' param to help AI focus the generated content Inline refine bar (replaces window.prompt): - Refine Body button shows/hides an amber inline input bar below generate buttons - Enter instructions, press Apply — no browser dialog, works in all contexts CSP: remove unsafe-inline from scriptSrc (security issue #2): - Converted all 26 onclick= handlers in component HTML files to data-action / data-target / data-label attributes - Added delegated click handler in app.js for copy/speak/nc-export actions - script-src now 'self' only; script-src-attr 'none' WebDAV path endpoint (security issue #5): - New POST /api/user/webdav-path (authMiddleware only, not moderator) - nextcloud.js updated to use new endpoint nodemailer: already at 6.10.1 (vulnerability fixed). --- package-lock.json | 236 ++++--------------------------- package.json | 2 +- public/components/chart.html | 6 +- public/components/cms.html | 24 +++- public/components/dictation.html | 6 +- public/components/encounter.html | 6 +- public/components/hospital.html | 6 +- public/components/sickvisit.html | 4 +- public/components/soap.html | 6 +- public/components/wellvisit.html | 18 +-- public/js/app.js | 25 ++++ public/js/learningHub.js | 43 ++++-- public/js/nextcloud.js | 2 +- server.js | 16 ++- 14 files changed, 147 insertions(+), 253 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00479d5..e91a14a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.16", "openai": "^4.73.0", - "pdf-parse": "^2.4.5", + "pdf-parse": "^1.1.1", "pg": "^8.13.0", "pptxgenjs": "^4.0.1", "qrcode": "^1.5.4", @@ -1010,190 +1010,6 @@ } } }, - "node_modules/@napi-rs/canvas": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", - "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", - "license": "MIT", - "workspaces": [ - "e2e/*" - ], - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.80", - "@napi-rs/canvas-darwin-arm64": "0.1.80", - "@napi-rs/canvas-darwin-x64": "0.1.80", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", - "@napi-rs/canvas-linux-arm64-musl": "0.1.80", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", - "@napi-rs/canvas-linux-x64-gnu": "0.1.80", - "@napi-rs/canvas-linux-x64-musl": "0.1.80", - "@napi-rs/canvas-win32-x64-msvc": "0.1.80" - } - }, - "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", - "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", - "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", - "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", - "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", - "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", - "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", - "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", - "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", - "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.80", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", - "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@puppeteer/browsers": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", @@ -4468,6 +4284,12 @@ "node": ">=10.5.0" } }, + "node_modules/node-ensure": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", + "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -4743,37 +4565,33 @@ "license": "MIT" }, "node_modules/pdf-parse": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", - "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", - "license": "Apache-2.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", + "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", + "license": "MIT", "dependencies": { - "@napi-rs/canvas": "0.1.80", - "pdfjs-dist": "5.4.296" - }, - "bin": { - "pdf-parse": "bin/cli.mjs" + "debug": "^3.1.0", + "node-ensure": "^0.0.0" }, "engines": { - "node": ">=20.16.0 <21 || >=22.3.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/mehmet-kozan" + "node": ">=6.8.1" } }, - "node_modules/pdfjs-dist": { - "version": "5.4.296", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", - "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.16.0 || >=22.3.0" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^0.1.80" + "node_modules/pdf-parse/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, + "node_modules/pdf-parse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/package.json b/package.json index b060740..7eb9c62 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.16", "openai": "^4.73.0", - "pdf-parse": "^2.4.5", + "pdf-parse": "^1.1.1", "pg": "^8.13.0", "pptxgenjs": "^4.0.1", "qrcode": "^1.5.4", diff --git a/public/components/chart.html b/public/components/chart.html index e91a6e9..cb0c928 100644 --- a/public/components/chart.html +++ b/public/components/chart.html @@ -90,9 +90,9 @@

Chart Review

- - - + + +
diff --git a/public/components/cms.html b/public/components/cms.html index 514b9b7..be4a985 100644 --- a/public/components/cms.html +++ b/public/components/cms.html @@ -157,10 +157,15 @@ PDF, TXT, MD, HTML — max 20 MB +
+ + +
diff --git a/public/components/dictation.html b/public/components/dictation.html index 86303b4..8603e94 100644 --- a/public/components/dictation.html +++ b/public/components/dictation.html @@ -69,9 +69,9 @@

Output

- - - + + +
diff --git a/public/components/encounter.html b/public/components/encounter.html index f8111be..ca368ac 100644 --- a/public/components/encounter.html +++ b/public/components/encounter.html @@ -66,9 +66,9 @@

Generated HPI

- - - + + +
diff --git a/public/components/hospital.html b/public/components/hospital.html index dc61649..386b90d 100644 --- a/public/components/hospital.html +++ b/public/components/hospital.html @@ -138,9 +138,9 @@
- - - + + +
diff --git a/public/components/sickvisit.html b/public/components/sickvisit.html index 52b4dc5..edafb0e 100644 --- a/public/components/sickvisit.html +++ b/public/components/sickvisit.html @@ -93,8 +93,8 @@
- - + +
diff --git a/public/components/soap.html b/public/components/soap.html index 524f067..74e813e 100644 --- a/public/components/soap.html +++ b/public/components/soap.html @@ -43,9 +43,9 @@

SOAP Note

- - - + + +
diff --git a/public/components/wellvisit.html b/public/components/wellvisit.html index 92fed34..52ffba2 100644 --- a/public/components/wellvisit.html +++ b/public/components/wellvisit.html @@ -93,10 +93,10 @@

Developmental Assessment

- + - - + +
@@ -109,7 +109,7 @@

Quick Summary

- +
@@ -149,8 +149,8 @@
- - + +
@@ -290,9 +290,9 @@
- - - + + +
diff --git a/public/js/app.js b/public/js/app.js index 502c47e..cb30b96 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -234,6 +234,31 @@ function setOutputText(el, text) { el.innerHTML = safe.replace(/\n/g, '
'); } +// ── Data-action event delegation (replaces inline onclick handlers) ── +// Handles data-action="copy|speak|nc-export" on any element, +// allowing removal of 'unsafe-inline' from the Content Security Policy. +document.addEventListener('click', function(e) { + var btn = e.target.closest('[data-action]'); + if (!btn) return; + var action = btn.getAttribute('data-action'); + var targetId = btn.getAttribute('data-target'); + if (!action) return; + + if (action === 'copy' && targetId) { + if (typeof copyText === 'function') copyText(targetId); + return; + } + if (action === 'speak' && targetId) { + if (typeof speakText === 'function') speakText(targetId); + return; + } + if (action === 'nc-export' && targetId) { + var label = btn.getAttribute('data-label') || 'export'; + if (typeof exportToNextcloud === 'function') exportToNextcloud(targetId, label); + return; + } +}); + // ── Announcement banner ──────────────────────────────────── function loadAnnouncement() { fetch('/api/admin/config/announcement', { headers: getAuthHeaders() }) diff --git a/public/js/learningHub.js b/public/js/learningHub.js index b1cc5cf..b7350e0 100644 --- a/public/js/learningHub.js +++ b/public/js/learningHub.js @@ -83,7 +83,9 @@ if (e.target.closest('#btn-lh-ai-open')) { openAiPanel(); return; } if (e.target.closest('#btn-lh-ai-close')) { closeAiPanel(); return; } if (e.target.closest('#btn-lh-ai-generate')) { runAiGenerate(); return; } - if (e.target.closest('#btn-lh-ai-refine-body')) { runAiRefineBody(); return; } + if (e.target.closest('#btn-lh-ai-refine-body')) { toggleRefineBar(); return; } + if (e.target.closest('#btn-lh-refine-submit')) { submitRefineBody(); return; } + if (e.target.closest('#btn-lh-refine-cancel')) { toggleRefineBar(false); return; } if (e.target.closest('#btn-lh-webdav-refresh')) { browseWebdav(_aiWebdavCurrentPath); return; } if (e.target.closest('#btn-lh-webdav-deselect')) { deselectWebdavFile(); return; } @@ -671,17 +673,22 @@ function selectWebdavFile(path, name) { _aiWebdavSelectedPath = path; _aiWebdavSelectedName = name; - var selDiv = document.getElementById('lh-ai-webdav-selected'); + var browser = document.getElementById('lh-ai-webdav-browser'); + var selDiv = document.getElementById('lh-ai-webdav-selected'); var selName = document.getElementById('lh-ai-webdav-selected-name'); - if (selDiv) selDiv.classList.remove('hidden'); - if (selName) selName.textContent = name; + // Hide browser, show selected indicator (use style.display — class="hidden" is overridden by inline styles) + if (browser) browser.style.display = 'none'; + if (selDiv) selDiv.style.display = 'flex'; + if (selName) selName.textContent = name; } function deselectWebdavFile() { _aiWebdavSelectedPath = ''; _aiWebdavSelectedName = ''; - var selDiv = document.getElementById('lh-ai-webdav-selected'); - if (selDiv) selDiv.classList.add('hidden'); + var browser = document.getElementById('lh-ai-webdav-browser'); + var selDiv = document.getElementById('lh-ai-webdav-selected'); + if (selDiv) selDiv.style.display = 'none'; + if (browser) browser.style.display = ''; } // ── Generate ──────────────────────────────────────────────── @@ -713,9 +720,13 @@ var fileInput = document.getElementById('lh-ai-file'); if (!fileInput || !fileInput.files[0]) { showToast('Select a file to upload', 'error'); return; } formData.append('file', fileInput.files[0]); + var uploadCtx = document.getElementById('lh-ai-upload-context'); + if (uploadCtx && uploadCtx.value.trim()) formData.append('topic', uploadCtx.value.trim()); } else if (tabName === 'webdav') { if (!_aiWebdavSelectedPath) { showToast('Select a file from Nextcloud', 'error'); return; } formData.append('webdavPath', _aiWebdavSelectedPath); + var webdavCtx = document.getElementById('lh-ai-webdav-context'); + if (webdavCtx && webdavCtx.value.trim()) formData.append('topic', webdavCtx.value.trim()); } var btn = document.getElementById('btn-lh-ai-generate'); @@ -784,21 +795,31 @@ } } - function runAiRefineBody() { - var instructions = prompt('How should the body be refined?\n(e.g., "Make it more concise", "Add a clinical case", "Focus on management")'); - if (!instructions || !instructions.trim()) return; + function toggleRefineBar(forceShow) { + var bar = document.getElementById('lh-ai-refine-bar'); + var input = document.getElementById('lh-ai-refine-input'); + if (!bar) return; + var show = forceShow !== undefined ? forceShow : bar.style.display === 'none'; + bar.style.display = show ? 'flex' : 'none'; + if (show && input) { input.value = ''; input.focus(); } + } + + function submitRefineBody() { + var input = document.getElementById('lh-ai-refine-input'); + var instructions = input ? input.value.trim() : ''; + if (!instructions) { showToast('Enter instructions for refinement', 'error'); return; } var currentBody = _bodyEditor ? getTpHTML(_bodyEditor) : ''; if (!currentBody) { showToast('No body content to refine', 'error'); return; } var model = document.getElementById('lh-ai-model') ? document.getElementById('lh-ai-model').value : getSelectedModel(); - + toggleRefineBar(false); showLoading('Refining content…'); fetch('/api/admin/learning/ai-refine', { method: 'POST', headers: getAuthHeaders(), - body: JSON.stringify({ content: currentBody, instructions: instructions.trim(), model: model }) + body: JSON.stringify({ content: currentBody, instructions: instructions, model: model }) }) .then(function(r) { return r.json(); }) .then(function(data) { diff --git a/public/js/nextcloud.js b/public/js/nextcloud.js index 9451be0..1304245 100644 --- a/public/js/nextcloud.js +++ b/public/js/nextcloud.js @@ -63,7 +63,7 @@ document.getElementById('btn-nc-save-path').addEventListener('click', function() { var p = (document.getElementById('nc-webdav-path').value || '').trim(); - fetch('/api/admin/learning/webdav-path', { + fetch('/api/user/webdav-path', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ path: p }) }).then(function(r) { return r.json(); }) .then(function(d) { d.success ? showToast('Path saved', 'success') : showToast(d.error || 'Failed', 'error'); }) diff --git a/server.js b/server.js index a042a4f..8c2fe29 100644 --- a/server.js +++ b/server.js @@ -19,8 +19,8 @@ app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], - scriptSrc: ["'self'", "'unsafe-inline'"], - scriptSrcAttr: ["'unsafe-inline'"], // allow onclick/onchange attribute handlers + scriptSrc: ["'self'"], + scriptSrcAttr: ["'none'"], styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'], fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'], imgSrc: ["'self'", 'data:', 'blob:'], @@ -156,6 +156,18 @@ app.use('/api', require('./src/routes/wellVisit')); app.use('/api', require('./src/routes/sickVisit')); app.use('/api/admin/learning', require('./src/routes/learningAI')); +// User-level preference: save WebDAV learning path (auth only, not moderator-only) +(function() { + var { authMiddleware } = require('./src/middleware/auth'); + var db = require('./src/db/database'); + app.post('/api/user/webdav-path', authMiddleware, async function(req, res) { + try { + await db.run('UPDATE users SET webdav_learning_path = ? WHERE id = ?', [req.body.path || null, req.user.id]); + res.json({ success: true }); + } catch(e) { res.status(500).json({ error: e.message }); } + }); +})(); + app.get('/', (req, res) => { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache');