From c8ae201e62929debed6e055eb7adfddbfe399534 Mon Sep 17 00:00:00 2001 From: Dicken Date: Mon, 15 Jun 2026 21:21:22 +0200 Subject: [PATCH] fix: Sync try-catch sauber, kein WITH RECURSIVE, zeigt Erfolg-Meldung --- backend/src/routes/admin.js | 194 +++++++++++++++++------------------- 1 file changed, 94 insertions(+), 100 deletions(-) diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 8ddab29..c541577 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -229,123 +229,117 @@ router.post('/webdav/test', authenticate, requireAdmin, async (req, res) => { // ── WebDAV Sync: alle lokalen Dateien auf NAS kopieren ──────────────────── router.post('/webdav/sync', authenticate, requireAdmin, async (req, res) => { const webdav = require('../tools/dateien/webdav'); - const path = require('path'); - const fs = require('fs'); + const nodePath = require('path'); + const fs = require('fs'); const UPLOAD_DIR = process.env.UPLOAD_DIR || '/data/uploads'; const cfg = webdav.getConfig(); if (!cfg.enabled) return res.status(400).json({ error: 'WebDAV nicht aktiviert' }); - // Ordnerpfad rekursiv aus DB auflösen - function getFolderPath(folderId) { - if (!folderId) return ''; - const parts = []; - let current = folderId; - const visited = new Set(); - while (current) { - if (visited.has(current)) break; - visited.add(current); - const folder = db.prepare('SELECT id, name, parent_id FROM folders WHERE id=?').get(current); - if (!folder) break; - parts.unshift(folder.name.replace(/[\/\\]/g, '_')); - current = folder.parent_id; - } - return parts.join('/'); - } - const results = { folders: 0, ok: 0, failed: 0, errors: [] }; - // ── Schritt 1: Alle User-Basisordner + Unterordner anlegen ─────────────── - const users = db.prepare('SELECT id, username FROM users ORDER BY id').all(); - - for (const user of users) { - const userBase = webdav.userPath(cfg, user.username); - - // User-Basisordner - try { await webdav.mkdirp(userBase); results.folders++; } catch(e) { - console.warn('[sync] mkdirp', userBase, e.message); + try { + // ── Ordnerpfad rekursiv auflösen ─────────────────────────────────────── + function getFolderPath(folderId) { + if (!folderId) return ''; + const parts = []; + let cur = folderId; + const seen = new Set(); + while (cur) { + if (seen.has(cur)) break; seen.add(cur); + const fo = db.prepare('SELECT name, parent_id FROM folders WHERE id=?').get(cur); + if (!fo) break; + parts.unshift(fo.name.replace(/[\/]/g, '_')); + cur = fo.parent_id; + } + return parts.join('/'); } - // Upload-Freigabe Ordner - try { await webdav.mkdirp(`${userBase}/_Upload-Freigaben`); results.folders++; } catch {} + // ── Schritt 1: User-Basisordner + alle DB-Ordner anlegen ─────────────── + const users = db.prepare('SELECT id, username FROM users ORDER BY id').all(); + for (const user of users) { + const userBase = webdav.userPath(cfg, user.username); + try { await webdav.mkdirp(userBase); results.folders++; } catch {} + try { await webdav.mkdirp(`${userBase}/_Upload-Freigaben`); } catch {} - // Alle DB-Ordner dieses Users (sortiert nach parent_id damit Parents zuerst) - const folders = db.prepare(` - WITH RECURSIVE tree AS ( - SELECT id, name, parent_id, 0 as depth FROM folders WHERE user_id=? AND parent_id IS NULL - UNION ALL - SELECT f.id, f.name, f.parent_id, t.depth+1 FROM folders f JOIN tree t ON f.parent_id=t.id - ) SELECT * FROM tree ORDER BY depth, id - `).all(user.id); - - for (const folder of folders) { - const folderPath = getFolderPath(folder.id); - const davPath = `${userBase}/${folderPath}`; - try { await webdav.mkdirp(davPath); results.folders++; } catch(e) { - results.errors.push(`Ordner ${davPath}: ${e.message}`); + // Ordner iterativ sortieren (flach, nach Tiefe) + const allFolders = db.prepare('SELECT id, name, parent_id FROM folders WHERE user_id=? ORDER BY id').all(user.id); + const depthCache = {}; + function depth(fid) { + if (!fid) return 0; + if (depthCache[fid] !== undefined) return depthCache[fid]; + const fo = allFolders.find(f => f.id === fid); + depthCache[fid] = fo ? 1 + depth(fo.parent_id) : 0; + return depthCache[fid]; + } + const sorted = [...allFolders].sort((a, b) => depth(a.id) - depth(b.id)); + for (const folder of sorted) { + const fp = getFolderPath(folder.id); + if (fp) try { await webdav.mkdirp(`${userBase}/${fp}`); results.folders++; } catch {} } } - } - // ── Schritt 2: Alle Dateien hochladen ──────────────────────────────────── - const files = db.prepare(` - SELECT f.*, u.username FROM files f - JOIN users u ON u.id = f.user_id - ORDER BY f.user_id, f.folder_id, f.id - `).all(); + // ── Schritt 2: Dateien hochladen ─────────────────────────────────────── + const files = db.prepare(` + SELECT f.*, u.username FROM files f + JOIN users u ON u.id = f.user_id + ORDER BY f.user_id, f.folder_id, f.id + `).all(); - for (const file of files) { - const localPath = path.join(UPLOAD_DIR, file.filename); - if (!fs.existsSync(localPath)) { - results.failed++; - results.errors.push(`${file.originalname}: lokale Datei fehlt`); - continue; + for (const file of files) { + const localPath = nodePath.join(UPLOAD_DIR, file.filename); + if (!fs.existsSync(localPath)) { + results.failed++; + results.errors.push(`${file.originalname}: lokale Datei fehlt`); + continue; + } + try { + const buf = fs.readFileSync(localPath); + const fp = getFolderPath(file.folder_id); + const base = webdav.userPath(cfg, file.username); + const davPath = fp ? `${base}/${fp}/${file.originalname}` : `${base}/${file.originalname}`; + await webdav.uploadFile(buf, davPath, file.mimetype || 'application/octet-stream'); + results.ok++; + } catch(e) { + results.failed++; + results.errors.push(`${file.originalname}: ${e.message}`); + } } - try { - const buf = fs.readFileSync(localPath); - const folderPath = getFolderPath(file.folder_id); - const userBase = webdav.userPath(cfg, file.username); - const davPath = folderPath - ? `${userBase}/${folderPath}/${file.originalname}` - : `${userBase}/${file.originalname}`; - await webdav.uploadFile(buf, davPath, file.mimetype || 'application/octet-stream'); - results.ok++; - } catch(e) { - results.failed++; - results.errors.push(`${file.originalname}: ${e.message}`); + + // ── Schritt 3: Upload-Freigabe Dateien ──────────────────────────────── + const uploadLogs = db.prepare(` + SELECT l.filename, l.originalname, l.mimetype, u.username + FROM upload_share_logs l + JOIN upload_shares s ON s.id = l.share_id + JOIN users u ON u.id = s.user_id + WHERE l.filename IS NOT NULL + `).all(); + + for (const log of uploadLogs) { + const localPath = nodePath.join(UPLOAD_DIR, log.filename); + if (!fs.existsSync(localPath)) continue; + try { + const buf = fs.readFileSync(localPath); + const base = webdav.userPath(cfg, log.username); + await webdav.uploadFile(buf, `${base}/_Upload-Freigaben/${log.originalname || log.filename}`, log.mimetype || 'application/octet-stream'); + results.ok++; + } catch(e) { + results.failed++; + results.errors.push(`Upload-Freigabe ${log.originalname}: ${e.message}`); + } } + + res.json({ + total: files.length + uploadLogs.length, + ...results, + message: `${results.folders} Ordner angelegt, ${results.ok} Dateien kopiert${results.failed ? ', ' + results.failed + ' fehlgeschlagen' : ' – alles erfolgreich ✓'}`, + }); + + } catch(err) { + console.error('[webdav sync error]', err); + res.status(500).json({ error: err.message, ...results }); } - - // ── Schritt 3: Upload-Freigabe Dateien ─────────────────────────────────── - const uploadLogs = db.prepare(` - SELECT l.filename, l.originalname, l.mimetype, l.size, - s.user_id, u.username - FROM upload_share_logs l - JOIN upload_shares s ON s.id = l.share_id - JOIN users u ON u.id = s.user_id - WHERE l.filename IS NOT NULL - `).all(); - - for (const log of uploadLogs) { - const localPath = path.join(UPLOAD_DIR, log.filename); - if (!fs.existsSync(localPath)) continue; - try { - const buf = fs.readFileSync(localPath); - const userBase = webdav.userPath(cfg, log.username); - const davPath = `${userBase}/_Upload-Freigaben/${log.originalname || log.filename}`; - await webdav.uploadFile(buf, davPath, log.mimetype || 'application/octet-stream'); - results.ok++; - } catch(e) { - results.failed++; - results.errors.push(`Upload-Freigabe ${log.originalname}: ${e.message}`); - } - } - - res.json({ - total: files.length + uploadLogs.length, - ...results, - message: `${results.folders} Ordner angelegt, ${results.ok} Dateien kopiert, ${results.failed} fehlgeschlagen` - }); }); + module.exports = router;