diff --git a/expressjs/src/server.js b/expressjs/src/server.js index 12b2609..506b292 100644 --- a/expressjs/src/server.js +++ b/expressjs/src/server.js @@ -352,7 +352,7 @@ function renderFileManagerPage(title, body) { .folder { font-weight: 700; color: var(--accent-strong); text-decoration: none; } .actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } .actions button { font-size: 0.9rem; padding: 7px 10px; } - dialog { border: none; border-radius: 16px; padding: 0; width: min(480px, 92vw); } + dialog { border: none; border-radius: 16px; padding: 0; width: min(520px, 92vw); } dialog::backdrop { background: rgba(15, 23, 42, 0.4); } .dialog-card { padding: 18px; display: grid; gap: 12px; } .dialog-actions { display: flex; gap: 10px; justify-content: flex-end; } @@ -990,6 +990,8 @@ app.get(`${basePath}/admin/files`, requireAdminPage, async (req, res) => { ${modifiedAt ? formatTimestamp(modifiedAt) : '—'} + + @@ -1106,14 +1108,56 @@ app.get(`${basePath}/admin/files`, requireAdminPage, async (req, res) => { + +
+ ${csrfField(res.locals.csrfToken)} + +

Verschieben

+
+ +
+ + +
+
+
+ + +
+ ${csrfField(res.locals.csrfToken)} + +

Kopieren

+
+ +
+ + +
+
+
+ @@ -1235,6 +1299,73 @@ app.post(`${basePath}/admin/files/delete`, requireAdminPage, async (req, res) => res.redirect(baseUrl(`/admin/files?path=${encodeURIComponent(nextPath)}`)); }); +app.post(`${basePath}/admin/files/move`, requireAdminPage, async (req, res) => { + const relativePath = String(req.body.path || '').replace(/^\/+/, ''); + const targetPath = String(req.body.targetPath || '').replace(/^\/+/, ''); + if (!relativePath || !targetPath) { + res.status(400).send(renderFileManagerPage('Admin-Dateien', '

Ungültige Eingabe.

')); + return; + } + const source = resolveAdminPath(relativePath); + const targetBase = resolveAdminPath(targetPath); + if (!source || !targetBase) { + res.status(400).send(renderFileManagerPage('Admin-Dateien', '

Ungültiger Pfad.

')); + return; + } + + let target = targetBase; + try { + const stat = await fs.promises.stat(targetBase); + if (stat.isDirectory()) { + target = path.join(targetBase, path.basename(source)); + } + } catch (err) { + // targetBase does not exist; treat as file/dir path. + } + + try { + await fs.promises.rename(source, target); + } catch (err) { + if (err.code === 'EXDEV') { + await fs.promises.cp(source, target, { recursive: true, force: false }); + await fs.promises.rm(source, { recursive: true, force: true }); + } else { + throw err; + } + } + await logEvent('admin_move', 'admin', { from: relativePath, to: targetPath }); + res.redirect(baseUrl('/admin/files')); +}); + +app.post(`${basePath}/admin/files/copy`, requireAdminPage, async (req, res) => { + const relativePath = String(req.body.path || '').replace(/^\/+/, ''); + const targetPath = String(req.body.targetPath || '').replace(/^\/+/, ''); + if (!relativePath || !targetPath) { + res.status(400).send(renderFileManagerPage('Admin-Dateien', '

Ungültige Eingabe.

')); + return; + } + const source = resolveAdminPath(relativePath); + const targetBase = resolveAdminPath(targetPath); + if (!source || !targetBase) { + res.status(400).send(renderFileManagerPage('Admin-Dateien', '

Ungültiger Pfad.

')); + return; + } + + let target = targetBase; + try { + const stat = await fs.promises.stat(targetBase); + if (stat.isDirectory()) { + target = path.join(targetBase, path.basename(source)); + } + } catch (err) { + // targetBase does not exist; treat as file/dir path. + } + + await fs.promises.cp(source, target, { recursive: true, force: false }); + await logEvent('admin_copy', 'admin', { from: relativePath, to: targetPath }); + res.redirect(baseUrl('/admin/files')); +}); + app.post(`${basePath}/admin/files/:id/delete`, requireAdminPage, async (req, res) => { const uploadEntry = await get('SELECT id, stored_path FROM uploads WHERE id = ?', [req.params.id]); if (!uploadEntry) {