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);
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' });