171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
|
|
import express from 'express';
|
||
|
|
import config from '../config.js';
|
||
|
|
import { query, queryOne, execute } from '../db.js';
|
||
|
|
import { deleteStored } from '../storage.js';
|
||
|
|
import { mintLink, allFilesWithOwner } from '../links.js';
|
||
|
|
import { getDiskInfo } from '../services/disk.js';
|
||
|
|
import { queueDepth } from '../services/compression.js';
|
||
|
|
import { requireAdmin, checkOrigin, setFlash } from '../auth.js';
|
||
|
|
import { adminOverviewPage, adminUsersPage, adminFilesPage } from '../views.js';
|
||
|
|
import { ah } from '../util.js';
|
||
|
|
import log from '../log.js';
|
||
|
|
|
||
|
|
const router = express.Router();
|
||
|
|
|
||
|
|
router.use(requireAdmin);
|
||
|
|
|
||
|
|
router.get(
|
||
|
|
'/',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
const [counts, disk] = await Promise.all([
|
||
|
|
queryOne(`SELECT
|
||
|
|
(SELECT COUNT(*) FROM users) AS users,
|
||
|
|
(SELECT COUNT(*) FROM files) AS files,
|
||
|
|
(SELECT COUNT(*) FROM download_links WHERE used = 0 AND expires_at > NOW()) AS activeLinks,
|
||
|
|
(SELECT COUNT(*) FROM download_links WHERE used = 1) AS usedLinks,
|
||
|
|
(SELECT COUNT(*) FROM download_links WHERE used = 0 AND expires_at <= NOW()) AS expiredLinks,
|
||
|
|
(SELECT COUNT(*) FROM files WHERE av_status = 'infected') AS infected`),
|
||
|
|
getDiskInfo(),
|
||
|
|
]);
|
||
|
|
res.send(
|
||
|
|
adminOverviewPage({
|
||
|
|
user: req.user,
|
||
|
|
stats: { ...counts, queueDepth: queueDepth() },
|
||
|
|
disk,
|
||
|
|
flash: res.locals.flash,
|
||
|
|
})
|
||
|
|
);
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.get(
|
||
|
|
'/users',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
const users = await query(
|
||
|
|
`SELECT u.id, u.username, u.email, u.is_admin, u.disabled, u.email_verified,
|
||
|
|
COUNT(f.id) AS file_count, COALESCE(SUM(f.size_bytes), 0) AS bytes_used
|
||
|
|
FROM users u
|
||
|
|
LEFT JOIN files f ON f.user_id = u.id
|
||
|
|
GROUP BY u.id
|
||
|
|
ORDER BY u.created_at DESC`
|
||
|
|
);
|
||
|
|
res.send(adminUsersPage({ user: req.user, users, flash: res.locals.flash }));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.get(
|
||
|
|
'/files',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
const files = await allFilesWithOwner();
|
||
|
|
res.send(adminFilesPage({ user: req.user, files, flash: res.locals.flash }));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
// ---- Mutations ----
|
||
|
|
router.use(checkOrigin);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/users/:id/verify',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
await execute(
|
||
|
|
'UPDATE users SET email_verified = 1, verify_token = NULL WHERE id = ?',
|
||
|
|
[req.params.id]
|
||
|
|
);
|
||
|
|
audit(req, 'admin.user.verify', { targetUser: req.params.id });
|
||
|
|
setFlash(req, 'ok', 'User verified.');
|
||
|
|
res.redirect(config.url('/admin/users'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/users/:id/disable',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
if (Number(req.params.id) === Number(req.user.id)) {
|
||
|
|
setFlash(req, 'error', 'You cannot disable your own account.');
|
||
|
|
return res.redirect(config.url('/admin/users'));
|
||
|
|
}
|
||
|
|
await execute('UPDATE users SET disabled = 1 WHERE id = ?', [req.params.id]);
|
||
|
|
audit(req, 'admin.user.disable', { targetUser: req.params.id });
|
||
|
|
setFlash(req, 'ok', 'User disabled.');
|
||
|
|
res.redirect(config.url('/admin/users'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/users/:id/enable',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
await execute('UPDATE users SET disabled = 0 WHERE id = ?', [req.params.id]);
|
||
|
|
audit(req, 'admin.user.enable', { targetUser: req.params.id });
|
||
|
|
setFlash(req, 'ok', 'User enabled.');
|
||
|
|
res.redirect(config.url('/admin/users'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/users/:id/delete',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
if (Number(req.params.id) === Number(req.user.id)) {
|
||
|
|
setFlash(req, 'error', 'You cannot delete your own account.');
|
||
|
|
return res.redirect(config.url('/admin/users'));
|
||
|
|
}
|
||
|
|
const files = await query('SELECT stored_name FROM files WHERE user_id = ?', [
|
||
|
|
req.params.id,
|
||
|
|
]);
|
||
|
|
for (const f of files) await deleteStored(f.stored_name);
|
||
|
|
await execute('DELETE FROM users WHERE id = ?', [req.params.id]); // cascades files+links
|
||
|
|
audit(req, 'admin.user.delete', { targetUser: req.params.id, files: files.length });
|
||
|
|
setFlash(req, 'ok', 'User and their files deleted.');
|
||
|
|
res.redirect(config.url('/admin/users'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/files/:id/delete',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
const file = await queryOne('SELECT stored_name FROM files WHERE id = ?', [
|
||
|
|
req.params.id,
|
||
|
|
]);
|
||
|
|
if (file) {
|
||
|
|
await deleteStored(file.stored_name);
|
||
|
|
await execute('DELETE FROM files WHERE id = ?', [req.params.id]);
|
||
|
|
audit(req, 'admin.file.delete', { fileId: req.params.id });
|
||
|
|
setFlash(req, 'ok', 'File deleted.');
|
||
|
|
}
|
||
|
|
res.redirect(config.url('/admin/files'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/files/:id/link',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
const file = await queryOne('SELECT id, av_status FROM files WHERE id = ?', [
|
||
|
|
req.params.id,
|
||
|
|
]);
|
||
|
|
if (!file) {
|
||
|
|
setFlash(req, 'error', 'File not found.');
|
||
|
|
return res.redirect(config.url('/admin/files'));
|
||
|
|
}
|
||
|
|
const token = await mintLink(file.id);
|
||
|
|
audit(req, 'admin.file.link', { fileId: file.id });
|
||
|
|
setFlash(req, 'ok', `New link: ${config.absoluteUrl(`/d/${token}`)}`);
|
||
|
|
res.redirect(config.url('/admin/files'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
router.post(
|
||
|
|
'/links/:id/expire',
|
||
|
|
ah(async (req, res) => {
|
||
|
|
await execute('UPDATE download_links SET expires_at = NOW() WHERE id = ?', [
|
||
|
|
req.params.id,
|
||
|
|
]);
|
||
|
|
audit(req, 'admin.link.expire', { linkId: req.params.id });
|
||
|
|
setFlash(req, 'ok', 'Link expired.');
|
||
|
|
res.redirect(config.url('/admin/files'));
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
function audit(req, action, meta) {
|
||
|
|
log.audit(`user#${req.user.id}(${req.user.username})`, action, meta);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default router;
|