mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-25 14:09:03 -04:00
157 lines
5.2 KiB
TypeScript
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;
|