PronounsPage/server/routes/translations.ts
2024-06-26 13:59:53 +02:00

157 lines
5.2 KiB
TypeScript

import { Router } from 'express';
import SQL from 'sql-template-strings';
import { ulid } from 'ulid';
import { findAdmins, handleErrorAsync } from '../../src/helpers.ts';
import { deduplicateEmailPreset } from './user.ts';
import auditLog from '../audit.ts';
const router = Router();
const TRANSLATION_STATUS = {
REJECTED: -1,
AWAITING: 0,
APPROVED: 1,
MERGED: 2,
};
router.post('/translations/propose', handleErrorAsync(async (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorised' });
}
for (const [tKey, tValue] of Object.entries(req.body.changes)) {
// TODO single insert
await req.db.get(SQL`INSERT INTO translations (id, locale, tKey, tValue, status, author_id) VALUES (
${ulid()}, ${global.config.locale},
${tKey}, ${JSON.stringify(tValue)},
${TRANSLATION_STATUS.AWAITING}, ${req.user.id}
)`);
}
await auditLog(req, 'translations/proposed', {
locale: global.config.locale,
autoApproved: req.isGranted('translations'),
changes: req.body.changes,
});
for (const { email } of await findAdmins(req.db, global.config.locale, 'translations')) {
await deduplicateEmailPreset(req.db, email, 'translationProposed', { locale: global.config.locale });
}
return res.json('OK');
}));
router.get('/translations/proposals', handleErrorAsync(async (req, res) => {
if (!req.isGranted('translations') && !req.isGranted('code')) {
return res.status(401).json({ error: 'Unauthorised' });
}
return res.json((await req.db.all<{ id: string, tKey: string, tValue: string, author: string }>(SQL`
SELECT t.id, t.tKey, t.tValue, u.username AS author, t.status
FROM translations t
LEFT JOIN users u ON t.author_id = u.id
WHERE locale = ${global.config.locale} AND (status = ${TRANSLATION_STATUS.AWAITING} OR status = ${TRANSLATION_STATUS.APPROVED})
`)).map((tp) => {
return {
...tp,
tValue: JSON.parse(tp.tValue),
};
}));
}));
router.post('/translations/reject-proposal', handleErrorAsync(async (req, res) => {
if (!req.isGranted('translations')) {
return res.status(401).json({ error: 'Unauthorised' });
}
await req.db.get(SQL`UPDATE translations SET status = ${TRANSLATION_STATUS.REJECTED} WHERE id = ${req.body.id}`);
await auditLog(req, 'translations/rejected', {
locale: global.config.locale,
id: req.body.id,
});
return res.json('OK');
}));
router.post('/translations/accept-proposal', handleErrorAsync(async (req, res) => {
if (!req.isGranted('translations')) {
return res.status(401).json({ error: 'Unauthorised' });
}
await req.db.get(SQL`UPDATE translations SET status = ${TRANSLATION_STATUS.APPROVED} WHERE id = ${req.body.id}`);
for (const { email } of await findAdmins(req.db, global.config.locale, 'code')) {
await deduplicateEmailPreset(req.db, email, 'translationToMerge', { locale: global.config.locale });
}
await auditLog(req, 'translations/accepted', {
locale: global.config.locale,
id: req.body.id,
});
return res.json('OK');
}));
router.post('/translations/unapprove-proposal', handleErrorAsync(async (req, res) => {
if (!req.isGranted('translations')) {
return res.status(401).json({ error: 'Unauthorised' });
}
await req.db.get(SQL`UPDATE translations SET status = ${TRANSLATION_STATUS.AWAITING} WHERE id = ${req.body.id}`);
await auditLog(req, 'translations/unapproved', {
locale: global.config.locale,
id: req.body.id,
});
return res.json('OK');
}));
router.post('/translations/proposals-done', handleErrorAsync(async (req, res) => {
if (!req.isGranted('code')) {
return res.status(401).json({ error: 'Unauthorised' });
}
await req.db.get(SQL`UPDATE translations SET status = ${TRANSLATION_STATUS.MERGED}
WHERE locale = ${global.config.locale} AND status = ${TRANSLATION_STATUS.APPROVED}`);
await auditLog(req, 'translations/merged', {
locale: global.config.locale,
id: req.body.id,
});
return res.json('OK');
}));
router.get('/translations/contributors', handleErrorAsync(async (req, res) => {
if (!req.isGranted('translations') && !req.isGranted('code')) {
return res.status(401).json({ error: 'Unauthorised' });
}
const contributors = [];
const translationCountByContributor = req.db.all<{ author_id: string, c: number }>(SQL`
SELECT author_id, count(*) AS c FROM translations
WHERE locale = ${global.config.locale} AND status >= ${TRANSLATION_STATUS.APPROVED}
GROUP BY author_id
`);
for (const { author_id: authorId, c } of await translationCountByContributor) {
const authorInfo = await req.db.get<{ username: string, roles: string }>(SQL`
SELECT username, roles FROM users WHERE id=${authorId}
`);
if (!authorInfo) {
continue;
}
contributors.push({
username: authorInfo.username,
isMember: !!authorInfo.roles,
count: c,
});
}
return res.json(contributors);
}));
export default router;