This repository has been archived on 2024-07-22. You can view files and clone it, but cannot push or open issues or pull requests.
Zaimki/server/routes/admin.js

266 lines
9.1 KiB
JavaScript
Raw Normal View History

2020-10-31 13:33:59 -07:00
import { Router } from 'express';
import SQL from 'sql-template-strings';
2020-11-02 12:12:15 -08:00
import avatar from '../avatar';
2022-01-03 10:22:10 -08:00
import {buildDict, now, shuffle, handleErrorAsync} from "../../src/helpers";
2021-01-12 11:06:59 -08:00
import locales from '../../src/locales';
2021-06-09 23:45:13 -07:00
import {calculateStats, statsFile} from '../../src/stats';
import fs from 'fs';
2021-07-02 16:15:44 -07:00
import { caches } from "../../src/cache";
2021-12-03 15:40:08 -08:00
import mailer from "../../src/mailer";
import {profilesSnapshot} from "./profile";
2022-01-03 10:22:10 -08:00
import buildLocaleList from "../../src/buildLocaleList";
2022-02-12 09:28:56 -08:00
import {archiveBan, liftBan} from "../ban";
2022-04-06 04:52:14 -07:00
import marked from 'marked';
import {loadCurrentUser} from "./user";
2020-10-31 13:33:59 -07:00
const router = Router();
router.get('/admin/list', handleErrorAsync(async (req, res) => {
2021-07-02 16:15:44 -07:00
return res.json(await caches.admins.fetch(async () => {
2021-06-17 14:29:47 -07:00
const admins = await req.db.all(SQL`
2021-11-23 06:05:14 -08:00
SELECT u.username, p.teamName, p.locale, u.id, u.email, u.avatarSource, p.credentials, p.credentialsLevel, p.credentialsName, a.payload
2021-06-17 14:29:47 -07:00
FROM users u
2021-11-23 06:05:14 -08:00
LEFT JOIN profiles p ON p.userId = u.id
LEFT JOIN authenticators a ON u.id = a.userId AND a.type = u.avatarSource AND (a.validUntil IS NULL OR a.validUntil > ${now()})
2021-06-17 14:29:47 -07:00
WHERE p.teamName IS NOT NULL
AND p.teamName != ''
ORDER BY RANDOM()
`);
2021-01-12 11:06:59 -08:00
2021-06-17 14:29:47 -07:00
const adminsGroupped = buildDict(function* () {
yield [global.config.locale, []];
2021-06-17 14:29:47 -07:00
for (let [locale, , , published] of locales) {
if (locale !== global.config.locale && published) {
2021-06-17 14:29:47 -07:00
yield [locale, []];
}
2021-01-12 11:06:59 -08:00
}
2021-06-17 14:29:47 -07:00
yield ['', []];
});
for (let admin of admins) {
admin.avatar = await avatar(req.db, admin);
delete admin.id;
delete admin.email;
2021-11-23 06:05:14 -08:00
delete admin.payload;
2021-08-26 05:25:59 -07:00
if (admin.credentials) {
admin.credentials = admin.credentials.split('|');
}
2021-01-12 11:06:59 -08:00
2021-06-17 14:29:47 -07:00
if (adminsGroupped[admin.locale] !== undefined) {
adminsGroupped[admin.locale].push(admin);
} else {
adminsGroupped[''].push(admin);
}
2021-01-12 11:06:59 -08:00
}
2021-06-17 14:29:47 -07:00
return adminsGroupped;
}));
}));
2021-01-12 11:06:59 -08:00
router.get('/admin/list/footer', handleErrorAsync(async (req, res) => {
2021-07-02 16:15:44 -07:00
return res.json(shuffle(await caches.adminsFooter.fetch(async () => {
2021-06-17 14:29:47 -07:00
const fromDb = await req.db.all(SQL`
SELECT u.username, p.footerName, p.footerAreas, p.locale
FROM users u
LEFT JOIN profiles p ON p.userId = u.id
WHERE p.locale = ${global.config.locale}
2021-06-17 14:29:47 -07:00
AND p.footerName IS NOT NULL AND p.footerName != ''
AND p.footerAreas IS NOT NULL AND p.footerAreas != ''
`);
2021-04-06 11:48:44 -07:00
const fromConfig = global.config.contact.authors || [];
2021-04-06 11:48:44 -07:00
2021-06-17 14:29:47 -07:00
return [...fromDb, ...fromConfig];
})));
}));
2021-01-12 11:06:59 -08:00
router.get('/admin/users', handleErrorAsync(async (req, res) => {
2020-12-30 15:03:30 -08:00
if (!req.isGranted('users')) {
2020-10-31 13:33:59 -07:00
return res.status(401).json({error: 'Unauthorised'});
}
const conditions = [];
let sql = SQL`
SELECT u.id, u.username, u.email, u.roles, u.avatarSource, group_concat(p.locale) AS profiles
2020-10-31 13:33:59 -07:00
FROM users u
LEFT JOIN profiles p ON p.userId = u.id
`
if (req.query.filter) {
conditions.push(SQL`u.username LIKE ${'%' + req.query.filter + '%'}`);
}
if (req.query.localeFilter) {
conditions.push(SQL`p.locale=${global.config.locale}`);
}
if (req.query.adminsFilter) {
conditions.push(SQL`u.roles != ''`);
}
let conditionsSql = SQL``;
if (conditions.length) {
let i = 0;
for (let condition of conditions) {
conditionsSql = conditionsSql.append(i++ ? SQL` AND ` : SQL` WHERE `).append(condition);
}
}
sql = sql.append(conditionsSql).append(SQL`
GROUP BY u.id
2020-12-30 15:03:30 -08:00
ORDER BY u.id DESC
LIMIT ${parseInt(req.query.limit || 100)}
OFFSET ${parseInt(req.query.offset || 0)}
2020-10-31 13:33:59 -07:00
`);
const countSql = SQL`SELECT COUNT(*) AS c FROM (SELECT u.id FROM users u LEFT JOIN profiles p ON p.userId = u.id`.append(conditionsSql).append(` GROUP BY u.id)`);
return res.json({
count: (await req.db.get(countSql)).c,
data: (await req.db.all(sql)).map(u => {
u.profiles = u.profiles ? u.profiles.split(',') : [];
return u;
}),
});
}));
2020-10-31 13:33:59 -07:00
router.get('/admin/stats', handleErrorAsync(async (req, res) => {
2020-12-30 15:03:30 -08:00
if (!req.isGranted('panel')) {
2020-11-24 15:54:02 -08:00
return res.status(401).json({error: 'Unauthorised'});
}
2021-06-09 23:45:13 -07:00
const stats = fs.existsSync(statsFile)
? JSON.parse(fs.readFileSync(statsFile))
: await calculateStats(req.db, buildLocaleList(global.config.locale));
2020-11-27 11:30:21 -08:00
2021-06-09 23:45:13 -07:00
for (let locale in stats.locales) {
if (stats.locales.hasOwnProperty(locale) && !req.isGranted('panel', locale)) {
delete stats.locales[locale];
2020-11-24 15:54:02 -08:00
}
}
2021-06-09 23:45:13 -07:00
return res.json(stats);
}));
2020-11-24 15:54:02 -08:00
2021-06-16 07:08:38 -07:00
const normalise = s => s.trim().toLowerCase();
router.post('/admin/ban/:username', handleErrorAsync(async (req, res) => {
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
2021-12-03 15:40:08 -08:00
const user = await req.db.get(SQL`SELECT id, email FROM users WHERE usernameNorm = ${normalise(req.params.username)}`);
2021-07-24 10:43:17 -07:00
if (!user) {
return res.status(400).json({error: 'No such user'});
}
2021-08-12 03:14:34 -07:00
if (req.body.reason) {
if (!req.body.terms.length) {
return res.status(400).json({error: 'Terms are required'});
}
await req.db.get(SQL`
UPDATE users
SET bannedReason = ${req.body.reason},
bannedTerms = ${req.body.terms.join(',')},
bannedBy = ${req.user.id},
banSnapshot = ${await profilesSnapshot(req.db, normalise(req.params.username))}
2021-08-12 03:14:34 -07:00
WHERE id = ${user.id}
`);
2022-02-12 09:28:56 -08:00
await archiveBan(req.db, user);
mailer(user.email, 'ban', {reason: req.body.reason, username: normalise(req.params.username)});
2021-08-12 03:14:34 -07:00
} else {
await req.db.get(SQL`
UPDATE users
SET bannedReason = null
WHERE id = ${user.id}
`);
2022-02-12 09:28:56 -08:00
await liftBan(req.db, user);
2021-08-12 03:14:34 -07:00
}
2021-07-24 10:43:17 -07:00
await req.db.get(SQL`
UPDATE reports
SET isHandled = 1
WHERE userId = ${user.id}
2021-06-16 07:08:38 -07:00
`);
return res.json(true);
}));
2021-07-24 10:18:39 -07:00
router.get('/admin/reports', handleErrorAsync(async (req, res) => {
2021-06-16 07:48:24 -07:00
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
return res.json(await req.db.all(SQL`
SELECT reports.id, group_concat(p.locale) as profiles, sus.username AS susUsername, reporter.username AS reporterUsername, reports.comment, reports.isAutomatic, reports.isHandled
2021-07-24 10:18:39 -07:00
FROM reports
LEFT JOIN users sus ON reports.userId = sus.id
LEFT JOIN users reporter ON reports.reporterId = reporter.id
LEFT JOIN profiles p on sus.id = p.userId
GROUP BY reports.id
ORDER BY min(reports.isHandled) ASC, reports.id DESC
2021-06-16 07:48:24 -07:00
`));
}));
2022-04-06 04:52:14 -07:00
router.get('/admin/reports/:id', handleErrorAsync(async (req, res) => {
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
return res.json(await req.db.all(SQL`
SELECT reports.id, sus.username AS susUsername, reporter.username AS reporterUsername, reports.comment, reports.isAutomatic, reports.isHandled
FROM reports
LEFT JOIN users sus ON reports.userId = sus.id
LEFT JOIN users reporter ON reports.reporterId = reporter.id
WHERE reports.userId = ${req.params.id}
ORDER BY reports.isHandled ASC, reports.id DESC
`));
}));
2021-07-24 10:18:39 -07:00
router.post('/admin/reports/handle/:id', handleErrorAsync(async (req, res) => {
2021-06-16 07:48:24 -07:00
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
await req.db.get(SQL`
2021-07-24 10:18:39 -07:00
UPDATE reports
SET isHandled = 1
2021-06-16 07:48:24 -07:00
WHERE id=${req.params.id}
`);
return res.json(true);
}));
2022-04-06 04:52:14 -07:00
router.get('/admin/moderation', handleErrorAsync(async (req, res) => {
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
const dir = __dirname + '/../../moderation';
const susRegexes = fs.readFileSync(dir + '/sus.txt').toString('utf-8').split('\n').filter(x => !!x);
const rulesUsers = marked(fs.readFileSync(dir + '/rules-users.md').toString('utf-8'));
const rulesTerminology = marked(fs.readFileSync(dir + '/rules-terminology.md').toString('utf-8'));
const rulesSources = marked(fs.readFileSync(dir + '/rules-sources.md').toString('utf-8'));
return res.json({
susRegexes,
rulesUsers,
rulesTerminology,
rulesSources,
})
}));
router.post('/admin/set-notification-frequency', handleErrorAsync(async (req, res) => {
if (!req.isGranted()) {
return res.status(401).json({error: 'Unauthorised'});
}
if (![0, 1, 7].includes(req.body.frequency)) {
return res.status(400).json({error: 'Bad request'});
}
await req.db.get(SQL`UPDATE users SET adminNotifications = ${req.body.frequency} WHERE id = ${req.user.id}`);
return await loadCurrentUser(req, res);
}));
2020-10-31 13:33:59 -07:00
export default router;