Dateien nach "backend/src/tools/dateien" hochladen

This commit is contained in:
2026-05-27 09:25:04 +02:00
parent 05ee3deae4
commit 6f28a09aa4

View File

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