diff --git a/backend/src/tools/dateien/routes.js b/backend/src/tools/dateien/routes.js index c5b0cf3..779bb7c 100644 --- a/backend/src/tools/dateien/routes.js +++ b/backend/src/tools/dateien/routes.js @@ -22,76 +22,60 @@ const getUpload = () => multer({ const allowed = (getSetting('file_allowed_ext') || '').split(',').map(e => e.trim().toLowerCase()).filter(Boolean); if (!allowed.length) return cb(null, true); const ext = path.extname(file.originalname).toLowerCase(); - if (allowed.includes(ext)) cb(null, true); - else cb(new Error(`Dateityp nicht erlaubt: ${ext}. Erlaubt: ${allowed.join(', ')}`)); + allowed.includes(ext) ? cb(null, true) : cb(new Error(`Dateityp nicht erlaubt: ${ext}`)); }, }); -const hasFileAccess = (fileId, userId) => { - const f = db.prepare('SELECT * FROM files WHERE id=?').get(fileId); - if (!f) return null; - if (f.user_id === userId) return f; - if (db.prepare('SELECT id FROM file_shares WHERE file_id=? AND shared_with=?').get(fileId, userId)) return f; - // Check folder share - if (f.folder_id && db.prepare('SELECT id FROM folder_shares WHERE folder_id=? AND shared_with=?').get(f.folder_id, userId)) return f; - return null; -}; - -// ── Ordner ──────────────────────────────────────────────────────────────────── -// Helper: collect all ancestor folder IDs that are shared with user -function getSharedRootFolders(uid) { - return db.prepare(` - SELECT f.*, u.username as owner, s.shared_by - FROM folders f - JOIN users u ON u.id=f.user_id - JOIN folder_shares s ON s.folder_id=f.id - WHERE s.shared_with=? - ORDER BY f.name - `).all(uid); -} - +// Walk folder tree upward to check if any ancestor is shared with user function isInSharedFolder(folderId, uid) { - // Walk up the folder tree to check if any ancestor is shared with uid - let current = folderId; - while (current) { - if (db.prepare('SELECT id FROM folder_shares WHERE folder_id=? AND shared_with=?').get(current, uid)) return true; - const parent = db.prepare('SELECT parent_id FROM folders WHERE id=?').get(current); - current = parent?.parent_id || null; + let cur = folderId; + while (cur) { + if (db.prepare('SELECT id FROM folder_shares WHERE folder_id=? AND shared_with=?').get(cur, uid)) return true; + const p = db.prepare('SELECT parent_id FROM folders WHERE id=?').get(cur); + cur = p?.parent_id || null; } return false; } -// GET /folders?parent_id= -router.get('/folders', authenticate, (req, res) => { - const uid = req.user.id; - const parentId = req.query.parent_id || null; - const own = db.prepare('SELECT * FROM folders WHERE user_id=? AND parent_id IS ? ORDER BY name').all(uid, parentId); +const enrichFolder = (folder) => ({ + ...folder, + fileCount: db.prepare('SELECT COUNT(*) c FROM files WHERE folder_id=?').get(folder.id).c, + subCount: db.prepare('SELECT COUNT(*) c FROM folders WHERE parent_id=?').get(folder.id).c, + totalSize: db.prepare('SELECT COALESCE(SUM(size),0) s FROM files WHERE folder_id=?').get(folder.id).s, +}); +// ── Ordner ──────────────────────────────────────────────────────────────────── +router.get('/folders', authenticate, (req, res) => { + const uid = req.user.id; + const parentId = req.query.parent_id ? parseInt(req.query.parent_id) : null; + + // Own folders in this directory + const own = parentId + ? db.prepare('SELECT * FROM folders WHERE user_id=? AND parent_id=? ORDER BY name').all(uid, parentId) + : db.prepare('SELECT * FROM folders WHERE user_id=? AND parent_id IS NULL ORDER BY name').all(uid); + + // Shared folders let shared = []; if (!parentId) { - // Root: directly shared folders - shared = getSharedRootFolders(uid); - } else { - // Inside a folder: show subfolders if parent is accessible - const parentOwned = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(parentId, uid); - if (!parentOwned && isInSharedFolder(parentId, uid)) { - // Show subfolders of shared folder - const parentFolder = db.prepare('SELECT * FROM folders WHERE id=?').get(parentId); - if (parentFolder) { - const subfolders = db.prepare('SELECT f.*, u.username as owner FROM folders f JOIN users u ON u.id=f.user_id WHERE f.parent_id=? AND f.user_id!=?').all(parentId, uid); - shared = subfolders; - } + shared = db.prepare(` + SELECT f.*, u.username as owner, + GROUP_CONCAT(su.username) as shared_with_names, + MIN(fs.created_at) as shared_at + FROM folders f JOIN users u ON u.id=f.user_id + JOIN folder_shares fs ON fs.folder_id=f.id + JOIN users su ON su.id=fs.shared_with + WHERE fs.shared_with=? + GROUP BY f.id ORDER BY f.name + `).all(uid); + } else if (isInSharedFolder(parentId, uid)) { + const parentFolder = db.prepare('SELECT * FROM folders WHERE id=?').get(parentId); + if (parentFolder) { + shared = db.prepare('SELECT f.*, u.username as owner FROM folders f JOIN users u ON u.id=f.user_id WHERE f.parent_id=? AND f.user_id!=? ORDER BY f.name').all(parentId, uid); } } - // Enrich folders with stats - const enrichFolder = (folder) => { - const fileCount = db.prepare('SELECT COUNT(*) c FROM files WHERE folder_id=?').get(folder.id).c; - const subCount = db.prepare('SELECT COUNT(*) c FROM folders WHERE parent_id=?').get(folder.id).c; - const totalSize = db.prepare('SELECT COALESCE(SUM(size),0) s FROM files WHERE folder_id=?').get(folder.id).s; - return { ...folder, fileCount, subCount, totalSize }; - }; - // Folders shared BY me - const sharedByMeFolders = parentId ? [] : db.prepare(` + + // Folders shared BY me (root only) + const sharedByMe = parentId ? [] : db.prepare(` SELECT DISTINCT f.*, u.username as owner, GROUP_CONCAT(su.username) as shared_with_names, MIN(fs.created_at) as shared_at @@ -102,55 +86,48 @@ router.get('/folders', authenticate, (req, res) => { GROUP BY f.id ORDER BY f.name `).all(uid); - res.json({ own: own.map(enrichFolder), shared: shared.map(enrichFolder), sharedByMe: sharedByMeFolders.map(enrichFolder) }); + res.json({ own: own.map(enrichFolder), shared: shared.map(enrichFolder), sharedByMe: sharedByMe.map(enrichFolder) }); }); -// POST /folders router.post('/folders', authenticate, (req, res) => { - const { name, parent_id=null } = req.body; + const { name, parent_id } = req.body; if (!name?.trim()) return res.status(400).json({ error: 'Name erforderlich' }); - if (parent_id) { - const parent = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(parent_id, req.user.id); + const parentId = parent_id ? parseInt(parent_id) : null; + if (parentId) { + const parent = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(parentId, req.user.id); if (!parent) return res.status(404).json({ error: 'Überordner nicht gefunden' }); } - const r = db.prepare('INSERT INTO folders (user_id,name,parent_id) VALUES (?,?,?)').run(req.user.id, name.trim(), parent_id); - res.json(db.prepare('SELECT * FROM folders WHERE id=?').get(r.lastInsertRowid)); + const r = db.prepare('INSERT INTO folders (user_id,name,parent_id) VALUES (?,?,?)').run(req.user.id, name.trim(), parentId); + res.json(enrichFolder(db.prepare('SELECT * FROM folders WHERE id=?').get(r.lastInsertRowid))); }); -// DELETE /folders/:id router.delete('/folders/:id', authenticate, (req, res) => { const folder = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!folder) return res.status(404).json({ error: 'Nicht gefunden' }); // Delete all files inside const files = db.prepare('SELECT * FROM files WHERE folder_id=? AND user_id=?').all(folder.id, req.user.id); - for (const f of files) { - try { fs.unlinkSync(path.join(UPLOAD_DIR, f.filename)); } catch {} - } + for (const f of files) { try { fs.unlinkSync(path.join(UPLOAD_DIR, f.filename)); } catch {} } db.prepare('DELETE FROM files WHERE folder_id=? AND user_id=?').run(folder.id, req.user.id); db.prepare('DELETE FROM folders WHERE id=?').run(folder.id); res.json({ success: true }); }); -// POST /folders/:id/share +router.get('/folders/:id/shares', authenticate, (req, res) => { + const folder = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(req.params.id, req.user.id); + if (!folder) return res.status(404).json({ error: 'Nicht gefunden' }); + res.json(db.prepare('SELECT u.id,u.username,fs.created_at as shared_at FROM folder_shares fs JOIN users u ON u.id=fs.shared_with WHERE fs.folder_id=?').all(folder.id)); +}); + router.post('/folders/:id/share', authenticate, (req, res) => { const folder = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!folder) return res.status(404).json({ error: 'Nicht gefunden' }); const target = db.prepare('SELECT * FROM users WHERE username=?').get(req.body.username); - if (!target) return res.status(404).json({ error: `Benutzer nicht gefunden` }); - if (target.id === req.user.id) return res.status(400).json({ error: 'Kannst nicht mit dir selbst teilen' }); + if (!target) return res.status(404).json({ error: 'Benutzer nicht gefunden' }); + if (target.id === req.user.id) return res.status(400).json({ error: 'Kann nicht mit dir selbst teilen' }); db.prepare('INSERT OR IGNORE INTO folder_shares (folder_id,shared_by,shared_with) VALUES (?,?,?)').run(folder.id, req.user.id, target.id); res.json({ success: true }); }); -// GET /folders/:id/shares -router.get('/folders/:id/shares', authenticate, (req, res) => { - const folder = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(req.params.id, req.user.id); - if (!folder) return res.status(404).json({ error: 'Nicht gefunden' }); - const shares = db.prepare('SELECT u.id,u.username FROM folder_shares s JOIN users u ON u.id=s.shared_with WHERE s.folder_id=?').all(folder.id); - res.json(shares); -}); - -// DELETE /folders/:id/share/:userId router.delete('/folders/:id/share/:userId', authenticate, (req, res) => { const folder = db.prepare('SELECT * FROM folders WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!folder) return res.status(404).json({ error: 'Nicht gefunden' }); @@ -159,11 +136,14 @@ router.delete('/folders/:id/share/:userId', authenticate, (req, res) => { }); // ── Dateien ─────────────────────────────────────────────────────────────────── -// GET /?folder_id= router.get('/', authenticate, (req, res) => { - const uid = req.user.id; - const folderId = req.query.folder_id || null; - const own = db.prepare('SELECT f.*, u.username as owner FROM files f JOIN users u ON u.id=f.user_id WHERE f.user_id=? AND f.folder_id IS ? ORDER BY f.created_at DESC').all(uid, folderId); + const uid = req.user.id; + const folderId = req.query.folder_id ? parseInt(req.query.folder_id) : null; + + // Own files in this directory + const own = folderId + ? db.prepare('SELECT f.*, u.username as owner FROM files f JOIN users u ON u.id=f.user_id WHERE f.user_id=? AND f.folder_id=? ORDER BY f.created_at DESC').all(uid, folderId) + : db.prepare('SELECT f.*, u.username as owner FROM files f JOIN users u ON u.id=f.user_id WHERE f.user_id=? AND f.folder_id IS NULL ORDER BY f.created_at DESC').all(uid); // Files shared BY me const sharedByMe = folderId ? [] : db.prepare(` @@ -177,11 +157,11 @@ router.get('/', authenticate, (req, res) => { GROUP BY f.id ORDER BY f.created_at DESC `).all(uid); + // Files shared with me let shared = []; if (!folderId) { - // Root: directly shared files shared = db.prepare(` - SELECT f.*, u.username as owner, s.shared_by, sb.username as shared_by_name + SELECT f.*, u.username as owner, sb.username as shared_by_name FROM files f JOIN users u ON u.id=f.user_id JOIN file_shares s ON s.file_id=f.id JOIN users sb ON sb.id=s.shared_by @@ -189,41 +169,43 @@ router.get('/', authenticate, (req, res) => { ORDER BY f.created_at DESC `).all(uid); } else if (isInSharedFolder(folderId, uid)) { - // Inside a shared folder: show files belonging to folder owner const folder = db.prepare('SELECT * FROM folders WHERE id=?').get(folderId); if (folder) { shared = db.prepare('SELECT f.*, u.username as owner FROM files f JOIN users u ON u.id=f.user_id WHERE f.folder_id=? AND f.user_id=? ORDER BY f.created_at DESC').all(folderId, folder.user_id); } } + res.json({ own, shared, sharedByMe }); }); -// POST / – Upload router.post('/', authenticate, (req, res) => { - const uid = req.user.id; + const uid = req.user.id; const maxCount = parseInt(getSetting('file_max_count') || '20'); - const count = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(uid).c; + const count = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(uid).c; if (count >= maxCount) return res.status(400).json({ error: `Maximum ${maxCount} Dateien erlaubt` }); getUpload().single('file')(req, res, err => { if (err) return res.status(400).json({ error: err.message }); if (!req.file) return res.status(400).json({ error: 'Keine Datei' }); - const folderId = req.body.folder_id || null; + const folderId = req.body.folder_id ? parseInt(req.body.folder_id) : null; const r = db.prepare('INSERT INTO files (user_id,filename,originalname,mimetype,size,folder_id) VALUES (?,?,?,?,?,?)') .run(uid, req.file.filename, req.file.originalname, req.file.mimetype||'application/octet-stream', req.file.size, folderId); res.json(db.prepare('SELECT f.*,u.username as owner FROM files f JOIN users u ON u.id=f.user_id WHERE f.id=?').get(r.lastInsertRowid)); }); }); -// GET /:id/download router.get('/:id/download', authenticate, (req, res) => { - const file = hasFileAccess(req.params.id, req.user.id); - if (!file) return res.status(403).json({ error: 'Kein Zugriff' }); - const filePath = path.join(UPLOAD_DIR, file.filename); - if (!fs.existsSync(filePath)) return res.status(404).json({ error: 'Datei fehlt' }); - res.download(filePath, file.originalname); + const uid = req.user.id; + const file = db.prepare('SELECT * FROM files WHERE id=?').get(req.params.id); + if (!file) return res.status(404).json({ error: 'Nicht gefunden' }); + const ok = file.user_id===uid + || db.prepare('SELECT id FROM file_shares WHERE file_id=? AND shared_with=?').get(file.id, uid) + || (file.folder_id && isInSharedFolder(file.folder_id, uid)); + if (!ok) return res.status(403).json({ error: 'Kein Zugriff' }); + const fp = path.join(UPLOAD_DIR, file.filename); + if (!fs.existsSync(fp)) return res.status(404).json({ error: 'Datei fehlt' }); + res.download(fp, file.originalname); }); -// DELETE /:id router.delete('/:id', authenticate, (req, res) => { const file = db.prepare('SELECT * FROM files WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!file) return res.status(404).json({ error: 'Nicht gefunden' }); @@ -232,25 +214,22 @@ router.delete('/:id', authenticate, (req, res) => { res.json({ success: true }); }); -// GET /:id/shares router.get('/:id/shares', authenticate, (req, res) => { const file = db.prepare('SELECT * FROM files WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!file) return res.status(404).json({ error: 'Nicht gefunden' }); - res.json(db.prepare('SELECT u.id,u.username FROM file_shares s JOIN users u ON u.id=s.shared_with WHERE s.file_id=?').all(file.id)); + res.json(db.prepare('SELECT u.id,u.username,s.created_at as shared_at FROM file_shares s JOIN users u ON u.id=s.shared_with WHERE s.file_id=?').all(file.id)); }); -// POST /:id/share router.post('/:id/share', authenticate, (req, res) => { const file = db.prepare('SELECT * FROM files WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!file) return res.status(404).json({ error: 'Nicht gefunden' }); const target = db.prepare('SELECT * FROM users WHERE username=?').get(req.body.username); - if (!target) return res.status(404).json({ error: `Benutzer nicht gefunden` }); + if (!target) return res.status(404).json({ error: 'Benutzer nicht gefunden' }); if (target.id === req.user.id) return res.status(400).json({ error: 'Kann nicht mit dir selbst teilen' }); db.prepare('INSERT OR IGNORE INTO file_shares (file_id,shared_by,shared_with) VALUES (?,?,?)').run(file.id, req.user.id, target.id); res.json({ success: true }); }); -// DELETE /:id/share/:userId router.delete('/:id/share/:userId', authenticate, (req, res) => { const file = db.prepare('SELECT * FROM files WHERE id=? AND user_id=?').get(req.params.id, req.user.id); if (!file) return res.status(404).json({ error: 'Nicht gefunden' });