diff --git a/backend/src/tools/dateien/routes.js b/backend/src/tools/dateien/routes.js index c95576c..8d879e5 100644 --- a/backend/src/tools/dateien/routes.js +++ b/backend/src/tools/dateien/routes.js @@ -15,26 +15,109 @@ const storage = multer.diskStorage({ destination: UPLOAD_DIR, filename: (req, file, cb) => cb(null, `${Date.now()}-${Math.random().toString(36).slice(2)}${path.extname(file.originalname)}`), }); - -const upload = multer({ +const getUpload = () => multer({ storage, - limits: { fileSize: () => parseInt(getSetting('file_max_size_mb') || '50') * 1024 * 1024 }, + limits: { fileSize: parseInt(getSetting('file_max_size_mb') || '50') * 1024 * 1024 }, fileFilter: (req, file, cb) => { - const allowed = (getSetting('file_allowed_ext') || '').split(',').map(e => e.trim().toLowerCase()); + 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.length || allowed.includes(ext)) cb(null, true); - else cb(new Error(`Dateityp nicht erlaubt: ${ext}`)); + if (allowed.includes(ext)) cb(null, true); + else cb(new Error(`Dateityp nicht erlaubt: ${ext}. Erlaubt: ${allowed.join(', ')}`)); }, }); -// GET / – eigene Dateien + freigegebene +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 ──────────────────────────────────────────────────────────────────── +// 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); + // Shared folders at root level + const shared = parentId ? [] : 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); + res.json({ own, shared }); +}); + +// POST /folders +router.post('/folders', authenticate, (req, res) => { + const { name, parent_id=null } = 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); + 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)); +}); + +// 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 {} + } + 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.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' }); + 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' }); + db.prepare('DELETE FROM folder_shares WHERE folder_id=? AND shared_with=?').run(folder.id, req.params.userId); + res.json({ success: true }); +}); + +// ── Dateien ─────────────────────────────────────────────────────────────────── +// GET /?folder_id= router.get('/', authenticate, (req, res) => { const uid = req.user.id; - 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=? ORDER BY f.created_at DESC').all(uid); - const shared = db.prepare(` + 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); + // Shared files (root only, folders handled separately) + const shared = folderId ? [] : db.prepare(` SELECT f.*, u.username as owner, s.shared_by, sb.username as shared_by_name - FROM files f - JOIN users u ON u.id=f.user_id + 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 WHERE s.shared_with=? @@ -43,49 +126,32 @@ router.get('/', authenticate, (req, res) => { res.json({ own, shared }); }); -// POST / – Datei hochladen +// POST / – Upload router.post('/', authenticate, (req, res) => { 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; if (count >= maxCount) return res.status(400).json({ error: `Maximum ${maxCount} Dateien erlaubt` }); - - upload.single('file')(req, res, err => { + 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 r = db.prepare('INSERT INTO files (user_id,filename,originalname,mimetype,size) VALUES (?,?,?,?,?)') - .run(uid, req.file.filename, req.file.originalname, req.file.mimetype || 'application/octet-stream', req.file.size); - 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)); + const folderId = 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 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' }); - // Zugriff: eigene Datei oder freigegeben - const hasAccess = file.user_id === uid || - db.prepare('SELECT id FROM file_shares WHERE file_id=? AND shared_with=?').get(file.id, uid); - if (!hasAccess) return res.status(403).json({ error: 'Kein Zugriff' }); + 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 auf Server' }); + if (!fs.existsSync(filePath)) return res.status(404).json({ error: 'Datei fehlt' }); res.download(filePath, file.originalname); }); -// GET /:id/shares – wer hat Zugriff auf diese Datei -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' }); - const shares = 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(shares); -}); - -// DELETE /:id – nur eigene Dateien +// 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' }); @@ -94,21 +160,25 @@ router.delete('/:id', authenticate, (req, res) => { res.json({ success: true }); }); -// POST /:id/share – Datei freigeben -router.post('/:id/share', authenticate, (req, res) => { +// 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 oder kein Zugriff' }); - const { username } = req.body; - const target = db.prepare('SELECT * FROM users WHERE username=?').get(username); - if (!target) return res.status(404).json({ error: `Benutzer "${username}" nicht gefunden` }); - if (target.id === req.user.id) return res.status(400).json({ error: 'Kannst du nicht mit dir selbst teilen' }); - try { - 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 }); - } catch(e) { res.status(400).json({ error: e.message }); } + 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)); }); -// DELETE /:id/share/:userId – Freigabe entfernen +// 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.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' }); @@ -116,17 +186,14 @@ router.delete('/:id/share/:userId', authenticate, (req, res) => { res.json({ success: true }); }); -// GET /settings – Admin-Einstellungen lesen +// Settings router.get('/settings', authenticate, requireAdmin, (req, res) => { const s = db.prepare('SELECT * FROM admin_settings').all(); const obj = {}; for (const r of s) obj[r.key] = r.value; res.json(obj); }); - -// PUT /settings – Admin-Einstellungen speichern router.put('/settings', authenticate, requireAdmin, (req, res) => { - const allowed = ['file_max_size_mb','file_max_count','file_allowed_ext']; - for (const key of allowed) { + for (const key of ['file_max_size_mb','file_max_count','file_allowed_ext']) { if (req.body[key] !== undefined) db.prepare('INSERT OR REPLACE INTO admin_settings (key,value) VALUES (?,?)').run(key, String(req.body[key])); }