183 lines
6.1 KiB
JavaScript
183 lines
6.1 KiB
JavaScript
import { Router } from 'express';
|
|
import SQL from 'sql-template-strings';
|
|
import avatar from '../avatar';
|
|
import {config as socialLoginConfig} from "../social";
|
|
import {buildDict, now, shuffle, sortByValue, handleErrorAsync} from "../../src/helpers";
|
|
import locales from '../../src/locales';
|
|
import {decodeTime} from "ulid";
|
|
|
|
const router = Router();
|
|
|
|
router.get('/admin/list', handleErrorAsync(async (req, res) => {
|
|
const admins = await req.db.all(SQL`
|
|
SELECT u.username, p.teamName, p.locale, u.id, u.email, u.avatarSource
|
|
FROM users u
|
|
LEFT JOIN profiles p ON p.userId = u.id
|
|
WHERE p.teamName IS NOT NULL AND p.teamName != ''
|
|
ORDER BY RANDOM()
|
|
`);
|
|
|
|
const adminsGroupped = buildDict(function*() {
|
|
yield [req.config.locale, []];
|
|
for (let [locale, , , published] of locales) {
|
|
if (locale !== req.config.locale && published) {
|
|
yield [locale, []];
|
|
}
|
|
}
|
|
yield ['', []];
|
|
});
|
|
for (let admin of admins) {
|
|
admin.avatar = await avatar(req.db, admin);
|
|
delete admin.id;
|
|
delete admin.email;
|
|
|
|
if (adminsGroupped[admin.locale] !== undefined) {
|
|
adminsGroupped[admin.locale].push(admin);
|
|
} else {
|
|
adminsGroupped[''].push(admin);
|
|
}
|
|
}
|
|
|
|
return res.json(adminsGroupped);
|
|
}));
|
|
|
|
router.get('/admin/list/footer', handleErrorAsync(async (req, res) => {
|
|
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 = ${req.config.locale}
|
|
AND p.footerName IS NOT NULL AND p.footerName != ''
|
|
AND p.footerAreas IS NOT NULL AND p.footerAreas != ''
|
|
`);
|
|
|
|
const fromConfig = req.config.contact.authors || [];
|
|
|
|
return res.json(shuffle([...fromDb, ...fromConfig]));
|
|
}));
|
|
|
|
router.get('/admin/users', handleErrorAsync(async (req, res) => {
|
|
if (!req.isGranted('users')) {
|
|
return res.status(401).json({error: 'Unauthorised'});
|
|
}
|
|
|
|
return res.json({});
|
|
|
|
const users = await req.db.all(SQL`
|
|
SELECT u.id, u.username, u.email, u.roles, u.avatarSource, p.locale
|
|
FROM users u
|
|
LEFT JOIN profiles p ON p.userId = u.id
|
|
ORDER BY u.id DESC
|
|
`);
|
|
|
|
const authenticators = await req.db.all(SQL`
|
|
SELECT userId, type FROM authenticators
|
|
WHERE type IN (`.append(Object.keys(socialLoginConfig).map(k => `'${k}'`).join(',')).append(SQL`)
|
|
AND (validUntil IS NULL OR validUntil > ${now()})
|
|
`));
|
|
|
|
const groupedUsers = {};
|
|
for (let user of users) {
|
|
if (groupedUsers[user.id] === undefined) {
|
|
groupedUsers[user.id] = {
|
|
...user,
|
|
locale: undefined,
|
|
profiles: user.locale ? [user.locale] : [],
|
|
avatar: await avatar(req.db, user),
|
|
socialConnections: [],
|
|
}
|
|
} else {
|
|
groupedUsers[user.id].profiles.push(user.locale);
|
|
}
|
|
}
|
|
|
|
for (let auth of authenticators) {
|
|
groupedUsers[auth.userId].socialConnections.push(auth.type);
|
|
}
|
|
|
|
return res.json(groupedUsers);
|
|
}));
|
|
|
|
const formatMonth = d => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
|
|
|
|
const buildChart = (rows) => {
|
|
const dates = rows.map(row => new Date(decodeTime(row.id)));
|
|
|
|
const chart = {};
|
|
|
|
let loop = dates[0];
|
|
const end = dates[dates.length - 1];
|
|
while(loop <= end){
|
|
chart[formatMonth(loop)] = 0;
|
|
loop = new Date(loop.setDate(loop.getDate() + 1));
|
|
}
|
|
chart[formatMonth(loop)] = 0;
|
|
|
|
for (let date of dates) {
|
|
chart[formatMonth(date)]++;
|
|
}
|
|
|
|
return chart;
|
|
}
|
|
|
|
router.get('/admin/stats', handleErrorAsync(async (req, res) => {
|
|
if (!req.isGranted('panel')) {
|
|
return res.status(401).json({error: 'Unauthorised'});
|
|
}
|
|
|
|
const users = {
|
|
overall: (await req.db.get(SQL`SELECT count(*) AS c FROM users`)).c,
|
|
admins: (await req.db.get(SQL`SELECT count(*) AS c FROM users WHERE roles!=''`)).c,
|
|
chart: buildChart(await req.db.all(SQL`SELECT id FROM users ORDER BY id`)),
|
|
};
|
|
|
|
const locales = {};
|
|
for (let locale in req.locales) {
|
|
if (!req.locales.hasOwnProperty(locale)) { continue; }
|
|
if (!req.isGranted('panel', locale)) { continue; }
|
|
const profiles = await req.db.all(SQL`SELECT pronouns, flags FROM profiles WHERE locale=${locale}`);
|
|
const pronouns = {}
|
|
const flags = {}
|
|
for (let profile of profiles) {
|
|
const pr = JSON.parse(profile.pronouns);
|
|
for (let pronoun in pr) {
|
|
if (!pr.hasOwnProperty(pronoun)) { continue; }
|
|
|
|
if (pronoun.includes(',') || pr[pronoun] < 0) {
|
|
continue;
|
|
}
|
|
const p = pronoun.replace(/^.*:\/\//, '').replace(/^\//, '').toLowerCase().replace(/^[a-z]+\.[^/]+\//, '');
|
|
if (pronouns[p] === undefined) {
|
|
pronouns[p] = 0;
|
|
}
|
|
pronouns[p] += pr[pronoun] === 1 ? 1 : 0.5;
|
|
}
|
|
|
|
const fl = JSON.parse(profile.flags);
|
|
for (let flag of fl) {
|
|
if (flags[flag] === undefined) {
|
|
flags[flag] = 0;
|
|
}
|
|
flags[flag] += 1;
|
|
}
|
|
}
|
|
|
|
locales[locale] = {
|
|
name: req.locales[locale].name,
|
|
url: req.locales[locale].url,
|
|
profiles: profiles.length,
|
|
pronouns: sortByValue(pronouns, true),
|
|
flags: sortByValue(flags, true),
|
|
nouns: {
|
|
approved: (await req.db.get(SQL`SELECT count(*) AS c FROM nouns WHERE locale=${locale} AND approved=1 AND deleted=0`)).c,
|
|
awaiting: (await req.db.get(SQL`SELECT count(*) AS c FROM nouns WHERE locale=${locale} AND approved=0 AND deleted=0`)).c,
|
|
},
|
|
chart: buildChart(await req.db.all(SQL`SELECT id FROM profiles WHERE locale=${locale} ORDER BY id`)),
|
|
};
|
|
}
|
|
|
|
return res.json({ users, locales });
|
|
}));
|
|
|
|
export default router;
|