Improve folder access admin dashboard
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 20s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 20s
This commit is contained in:
@@ -90,6 +90,18 @@ function listSubfolders(dir) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFolderInfo(folder) {
|
||||
const fullPath = path.join(FULLS_DIR, folder);
|
||||
const thumbPath = path.join(THUMBS_DIR, folder);
|
||||
const images = listImagesInDir(fullPath);
|
||||
return {
|
||||
name: folder,
|
||||
image_count: images.length,
|
||||
thumb_count: listImagesInDir(thumbPath).length,
|
||||
sample: images[0] || null
|
||||
};
|
||||
}
|
||||
|
||||
function isSafePath(baseDir, requestedPath) {
|
||||
const resolvedBase = path.resolve(baseDir);
|
||||
const resolvedPath = path.resolve(resolvedBase, requestedPath);
|
||||
@@ -206,29 +218,52 @@ app.get('/images/fulls/', (req, res) => {
|
||||
const rootImages = listImagesInDir(FULLS_DIR).map(name => ({
|
||||
name, type: 'file', folder: null
|
||||
}));
|
||||
const requestedFolder = req.query.folder;
|
||||
|
||||
if (requestedFolder) {
|
||||
if (!isValidName(requestedFolder)) {
|
||||
return res.status(400).json({ error: 'Ungueltiger Ordnername' });
|
||||
}
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Nicht eingeloggt' });
|
||||
}
|
||||
|
||||
const sendFolderImages = () => {
|
||||
const folderPath = path.join(FULLS_DIR, requestedFolder);
|
||||
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
||||
return res.status(404).json({ error: 'Ordner nicht gefunden' });
|
||||
}
|
||||
return res.json(listImagesInDir(folderPath).map(name => ({
|
||||
name, type: 'file', folder: requestedFolder
|
||||
})));
|
||||
};
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
return sendFolderImages();
|
||||
}
|
||||
|
||||
return db.get('SELECT 1 FROM user_folders WHERE user_id = ? AND folder_name = ?',
|
||||
[req.user.id, requestedFolder], (err, row) => {
|
||||
if (err || !row) return res.status(403).json({ error: 'Kein Zugriff auf diesen Ordner' });
|
||||
return sendFolderImages();
|
||||
});
|
||||
}
|
||||
|
||||
// Nicht eingeloggt: nur Root-Bilder
|
||||
if (!req.user) return res.json(rootImages);
|
||||
|
||||
// Eingeloggt: Startseite bleibt die normale oeffentliche Galerie.
|
||||
return res.json(rootImages);
|
||||
});
|
||||
|
||||
app.get('/api/folders/available', requireAuth, (req, res) => {
|
||||
const folders = listSubfolders(FULLS_DIR);
|
||||
|
||||
// Admin: Nur Root-Bilder (damit die Startseite sauber bleibt)
|
||||
if (req.user.role === 'admin') {
|
||||
return res.json(rootImages);
|
||||
return res.json(folders.map(getFolderInfo));
|
||||
}
|
||||
|
||||
// User/Client: NUR zugewiesene Ordner (keine Root-Bilder untermischen)
|
||||
db.all('SELECT folder_name FROM user_folders WHERE user_id = ?', [req.user.id], (err, rows) => {
|
||||
const assignedFolders = err ? [] : rows.map(r => r.folder_name);
|
||||
const result = [];
|
||||
assignedFolders.forEach(folder => {
|
||||
if (folders.includes(folder)) {
|
||||
listImagesInDir(path.join(FULLS_DIR, folder)).forEach(name => {
|
||||
result.push({ name, type: 'file', folder });
|
||||
});
|
||||
}
|
||||
});
|
||||
return res.json(result);
|
||||
return res.json(assignedFolders.filter(folder => folders.includes(folder)).map(getFolderInfo));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -244,8 +279,17 @@ app.get('/api/admin/users', requireAdmin, (req, res) => {
|
||||
res.json(rows2.map(u => ({ id: u.id, username: u.username, role: u.role || 'client', created_at: u.created_at || null })));
|
||||
});
|
||||
}
|
||||
// Ablaufdatum berechnen fuer Clients
|
||||
const result = rows.map(u => {
|
||||
db.all('SELECT user_id, folder_name FROM user_folders ORDER BY folder_name', (folderErr, folderRows) => {
|
||||
const foldersByUser = {};
|
||||
if (!folderErr && folderRows) {
|
||||
folderRows.forEach(row => {
|
||||
if (!foldersByUser[row.user_id]) foldersByUser[row.user_id] = [];
|
||||
foldersByUser[row.user_id].push(row.folder_name);
|
||||
});
|
||||
}
|
||||
|
||||
// Ablaufdatum berechnen fuer Clients
|
||||
const result = rows.map(u => {
|
||||
const out = { ...u };
|
||||
if (u.role === 'client' && u.created_at) {
|
||||
const expires = new Date(u.created_at);
|
||||
@@ -253,9 +297,11 @@ app.get('/api/admin/users', requireAdmin, (req, res) => {
|
||||
out.expires_at = expires.toISOString();
|
||||
out.expired = new Date() > expires;
|
||||
}
|
||||
out.folders = foldersByUser[u.id] || [];
|
||||
return out;
|
||||
});
|
||||
res.json(result);
|
||||
});
|
||||
res.json(result);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -298,7 +344,49 @@ app.put('/api/admin/users/:id/password', requireAdmin, (req, res) => {
|
||||
|
||||
// Ordner-Verwaltung
|
||||
app.get('/api/admin/folders', requireAdmin, (req, res) => {
|
||||
res.json(listSubfolders(FULLS_DIR));
|
||||
const folders = listSubfolders(FULLS_DIR).map(getFolderInfo);
|
||||
db.all(
|
||||
`SELECT uf.folder_name, u.id, u.username, u.role
|
||||
FROM user_folders uf
|
||||
JOIN users u ON u.id = uf.user_id
|
||||
ORDER BY uf.folder_name, u.username`,
|
||||
(err, rows) => {
|
||||
const assignedByFolder = {};
|
||||
if (!err && rows) {
|
||||
rows.forEach(row => {
|
||||
if (!assignedByFolder[row.folder_name]) assignedByFolder[row.folder_name] = [];
|
||||
assignedByFolder[row.folder_name].push({
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
role: row.role || 'client'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
res.json(folders.map(folder => ({
|
||||
...folder,
|
||||
assigned_users: assignedByFolder[folder.name] || [],
|
||||
private: true
|
||||
})));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
app.get('/api/admin/folders/:folderName/images', requireAdmin, (req, res) => {
|
||||
const folderName = req.params.folderName;
|
||||
if (!isValidName(folderName)) {
|
||||
return res.status(400).json({ error: 'Ungueltiger Ordnername' });
|
||||
}
|
||||
const folderPath = path.join(FULLS_DIR, folderName);
|
||||
if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
|
||||
return res.status(404).json({ error: 'Ordner nicht gefunden' });
|
||||
}
|
||||
res.json(listImagesInDir(folderPath).map(name => ({
|
||||
name,
|
||||
folder: folderName,
|
||||
full_url: '/images/fulls/' + encodeURIComponent(folderName) + '/' + encodeURIComponent(name),
|
||||
thumb_url: '/images/thumbs/' + encodeURIComponent(folderName) + '/' + encodeURIComponent(name)
|
||||
})));
|
||||
});
|
||||
|
||||
app.get('/api/admin/assign', requireAdmin, (req, res) => {
|
||||
@@ -317,7 +405,10 @@ app.get('/api/admin/assign', requireAdmin, (req, res) => {
|
||||
app.post('/api/admin/assign', requireAdmin, (req, res) => {
|
||||
const { user_id, folders } = req.body;
|
||||
if (!user_id) return res.status(400).json({ error: 'user_id erforderlich' });
|
||||
const folderList = Array.isArray(folders) ? folders : [];
|
||||
const existingFolders = listSubfolders(FULLS_DIR);
|
||||
const folderList = Array.isArray(folders)
|
||||
? folders.filter(name => isValidName(name) && existingFolders.includes(name))
|
||||
: [];
|
||||
db.serialize(() => {
|
||||
db.run('DELETE FROM user_folders WHERE user_id = ?', [user_id]);
|
||||
const stmt = db.prepare('INSERT INTO user_folders (user_id, folder_name) VALUES (?, ?)');
|
||||
@@ -408,6 +499,10 @@ app.post('/api/download/selected', (req, res) => {
|
||||
} else {
|
||||
// Ordner-Bild: nur wenn User den Ordner zugewiesen hat
|
||||
if (!req.user) return checkAccess(false);
|
||||
if (req.user.role === 'admin') {
|
||||
const filePath = path.join(FULLS_DIR, folder, name);
|
||||
return checkAccess(fs.existsSync(filePath));
|
||||
}
|
||||
db.get('SELECT 1 FROM user_folders WHERE user_id = ? AND folder_name = ?',
|
||||
[req.user.id, folder], (err, row) => {
|
||||
if (err || !row) return checkAccess(false);
|
||||
@@ -453,30 +548,37 @@ app.get('/api/download/folder/:folderName', (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Nicht eingeloggt' });
|
||||
}
|
||||
if (req.user.role === 'admin') {
|
||||
return sendFolderDownload(folderName, res);
|
||||
}
|
||||
db.get('SELECT 1 FROM user_folders WHERE user_id = ? AND folder_name = ?',
|
||||
[req.user.id, folderName], (err, row) => {
|
||||
if (err || !row) {
|
||||
return res.status(403).json({ error: 'Kein Zugriff auf diesen Ordner' });
|
||||
}
|
||||
const folderPath = path.join(FULLS_DIR, folderName);
|
||||
const images = listImagesInDir(folderPath);
|
||||
if (images.length === 0) {
|
||||
return res.status(404).json({ error: 'Keine Bilder im Ordner' });
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/zip');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(folderName) + '.zip"');
|
||||
const archive = archiver('zip', { zlib: { level: 5 } });
|
||||
archive.on('error', () => {
|
||||
if (!res.headersSent) res.status(500).json({ error: 'ZIP-Fehler' });
|
||||
});
|
||||
archive.pipe(res);
|
||||
images.forEach(name => {
|
||||
archive.file(path.join(folderPath, name), { name });
|
||||
});
|
||||
archive.finalize();
|
||||
return sendFolderDownload(folderName, res);
|
||||
});
|
||||
});
|
||||
|
||||
function sendFolderDownload(folderName, res) {
|
||||
const folderPath = path.join(FULLS_DIR, folderName);
|
||||
const images = listImagesInDir(folderPath);
|
||||
if (images.length === 0) {
|
||||
return res.status(404).json({ error: 'Keine Bilder im Ordner' });
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/zip');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(folderName) + '.zip"');
|
||||
const archive = archiver('zip', { zlib: { level: 5 } });
|
||||
archive.on('error', () => {
|
||||
if (!res.headersSent) res.status(500).json({ error: 'ZIP-Fehler' });
|
||||
});
|
||||
archive.pipe(res);
|
||||
images.forEach(name => {
|
||||
archive.file(path.join(folderPath, name), { name });
|
||||
});
|
||||
archive.finalize();
|
||||
}
|
||||
|
||||
// ==================== STATIC FILES ====================
|
||||
|
||||
app.get(/^\/images\/(fulls|thumbs)\/(.+)$/, serveProtectedImage);
|
||||
|
||||
Reference in New Issue
Block a user