[pl][census] moderation tool
This commit is contained in:
parent
7618b57074
commit
0fc7e762a2
|
@ -1335,9 +1335,17 @@ census:
|
||||||
Wyniki i wnioski ogłosimy wkrótce {/kontakt=w mediach społecznościowych}.
|
Wyniki i wnioski ogłosimy wkrótce {/kontakt=w mediach społecznościowych}.
|
||||||
prev: 'Poprzednie pytanie'
|
prev: 'Poprzednie pytanie'
|
||||||
next: 'Następne pytanie'
|
next: 'Następne pytanie'
|
||||||
replies: 'odpowiedzi'
|
replies: 'odpowiedzi, w tym:'
|
||||||
|
repliesNonbinary: 'od osób niebinarnych/poszukujących'
|
||||||
|
repliesUsable: 'użytecznych'
|
||||||
|
repliesAwaiting: 'oczekujących na moderację'
|
||||||
writein: 'Jeśli Twojej odpowiedzi nie ma powyżej, możesz ją wpisać tutaj.'
|
writein: 'Jeśli Twojej odpowiedzi nie ma powyżej, możesz ją wpisać tutaj.'
|
||||||
leave: 'Czy na pewno chcesz wyjść? Twoje dotychczasowe odpowiedzi nie zostaną zapisane!'
|
leave: 'Czy na pewno chcesz wyjść? Twoje dotychczasowe odpowiedzi nie zostaną zapisane!'
|
||||||
|
moderation:
|
||||||
|
troll: 'Troll'
|
||||||
|
skip: 'Nie wiem, pomiń'
|
||||||
|
ok: 'Nie troll'
|
||||||
|
done: 'Wszystkie odpowiedzi są już przejrzane!'
|
||||||
|
|
||||||
share: 'Udostępnij'
|
share: 'Udostępnij'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
-- Up
|
||||||
|
|
||||||
|
ALTER TABLE census ADD COLUMN troll INTEGER NULL;
|
||||||
|
|
||||||
|
-- Down
|
||||||
|
|
|
@ -264,6 +264,7 @@ export default {
|
||||||
|
|
||||||
if (config.census.enabled) {
|
if (config.census.enabled) {
|
||||||
routes.push({ path: '/' + encodeURIComponent(config.census.route), component: resolve(__dirname, 'routes/census.vue') });
|
routes.push({ path: '/' + encodeURIComponent(config.census.route), component: resolve(__dirname, 'routes/census.vue') });
|
||||||
|
routes.push({ path: '/' + encodeURIComponent(config.census.route) + '/admin', component: resolve(__dirname, 'routes/censusModeration.vue') });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.user.enabled) {
|
if (config.user.enabled) {
|
||||||
|
|
|
@ -10,12 +10,16 @@
|
||||||
<template v-if="q === null">
|
<template v-if="q === null">
|
||||||
<section v-if="$isGranted('census')">
|
<section v-if="$isGranted('census')">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
{{countResponses}}
|
|
||||||
<T>census.replies</T>
|
|
||||||
|
|
||||||
<a href="/api/census/export" class="btn btn-outline-secondary btn-sm float-end">
|
<a href="/api/census/export" class="btn btn-outline-secondary btn-sm float-end">
|
||||||
<Icon v="download"/>
|
<Icon v="download"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<p>{{countResponses.all}} <T>census.replies</T></p>
|
||||||
|
<ul>
|
||||||
|
<li>{{countResponses.nonbinary}} <T>census.repliesNonbinary</T></li>
|
||||||
|
<li>{{countResponses.usable}} <T>census.repliesUsable</T></li>
|
||||||
|
<li><nuxt-link :to="`/${config.census.route}/admin`">{{countResponses.awaiting}} <T>census.repliesAwaiting</T></nuxt-link></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="$isGranted('census')">
|
||||||
|
<CommunityNav/>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<Icon v="user-chart"/>
|
||||||
|
<T>census.headerLong</T>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Spinner v-if="queue === undefined" size="5rem"/>
|
||||||
|
<div v-else-if="queue.count === 0" class="alert alert-success text-center">
|
||||||
|
<Icon v="check-circle" size="5"/>
|
||||||
|
<p><T>census.moderation.done</T></p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{queue.count}} <T>census.repliesAwaiting</T>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li v-for="(question, i) in config.census.questions">
|
||||||
|
<p>{{question.question}}</p>
|
||||||
|
<p><strong>{{queue.next.answers[i.toString()]}}</strong></p>
|
||||||
|
<p v-if="queue.next.writins[i.toString()]"><strong><em>{{queue.next.writins[i.toString()]}}</em></strong></p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="d-flex my-5">
|
||||||
|
<button class="btn btn-danger flex-grow-1 m-2" @click="decide(true)"><T>census.moderation.troll</T></button>
|
||||||
|
<button class="btn btn-outline-primary flex-grow-1 m-2" @click="skip()"><T>census.moderation.skip</T></button>
|
||||||
|
<button class="btn btn-success flex-grow-1 m-2" @click="decide(false)"><T>census.moderation.ok</T></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NotFound v-else/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
queue: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetch() {
|
||||||
|
this.queue = await this.$axios.$get('/census/moderation/queue');
|
||||||
|
if (this.queue.next) {
|
||||||
|
this.queue.next.answers = JSON.parse(this.queue.next.answers);
|
||||||
|
this.queue.next.writins = JSON.parse(this.queue.next.writins);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async decide(decision) {
|
||||||
|
const id = this.queue.next.id;
|
||||||
|
this.queue = undefined;
|
||||||
|
await this.$post('/census/moderation/decide', {
|
||||||
|
id,
|
||||||
|
decision: decision ? 1 : 0,
|
||||||
|
})
|
||||||
|
await this.fetch();
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
},
|
||||||
|
async skip() {
|
||||||
|
this.queue = undefined;
|
||||||
|
await this.fetch();
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -69,11 +69,39 @@ router.post('/census/submit', handleErrorAsync(async (req, res) => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/census/count', handleErrorAsync(async (req, res) => {
|
router.get('/census/count', handleErrorAsync(async (req, res) => {
|
||||||
return res.json((await req.db.get(SQL`
|
if (!req.isGranted('census')) {
|
||||||
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// duplication reason: https://github.com/felixfbecker/node-sql-template-strings/issues/71
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
all: (await req.db.get(SQL`
|
||||||
SELECT COUNT(*) as c FROM census
|
SELECT COUNT(*) as c FROM census
|
||||||
WHERE locale = ${global.config.locale}
|
WHERE locale = ${global.config.locale}
|
||||||
AND edition = ${global.config.census.edition}
|
AND edition = ${global.config.census.edition}
|
||||||
`)).c);
|
`)).c,
|
||||||
|
nonbinary: (await req.db.get(SQL`
|
||||||
|
SELECT COUNT(*) as c FROM census
|
||||||
|
WHERE locale = ${global.config.locale}
|
||||||
|
AND edition = ${global.config.census.edition}
|
||||||
|
AND (answers LIKE '{"0":"osobą niebinarną"%' OR answers LIKE '{"0":"nie wiem"%') -- TODO polish-specific
|
||||||
|
`)).c,
|
||||||
|
usable: (await req.db.get(SQL`
|
||||||
|
SELECT COUNT(*) as c FROM census
|
||||||
|
WHERE locale = ${global.config.locale}
|
||||||
|
AND edition = ${global.config.census.edition}
|
||||||
|
AND (answers LIKE '{"0":"osobą niebinarną"%' OR answers LIKE '{"0":"nie wiem"%') -- TODO polish-specific
|
||||||
|
AND troll = 0
|
||||||
|
`)).c,
|
||||||
|
awaiting: (await req.db.get(SQL`
|
||||||
|
SELECT COUNT(*) as c FROM census
|
||||||
|
WHERE locale = ${global.config.locale}
|
||||||
|
AND edition = ${global.config.census.edition}
|
||||||
|
AND (answers LIKE '{"0":"osobą niebinarną"%' OR answers LIKE '{"0":"nie wiem"%') -- TODO polish-specific
|
||||||
|
AND troll IS NULL
|
||||||
|
`)).c,
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
router.get('/census/export', handleErrorAsync(async (req, res) => {
|
router.get('/census/export', handleErrorAsync(async (req, res) => {
|
||||||
|
@ -82,11 +110,12 @@ router.get('/census/export', handleErrorAsync(async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = [];
|
const report = [];
|
||||||
for (let {answers, writins} of await req.db.all(SQL`
|
for (let {answers, writins, troll} of await req.db.all(SQL`
|
||||||
SELECT answers, writins FROM census
|
SELECT answers, writins FROM census
|
||||||
WHERE locale = ${global.config.locale}
|
WHERE locale = ${global.config.locale}
|
||||||
AND edition = ${global.config.census.edition}
|
AND edition = ${global.config.census.edition}
|
||||||
AND suspicious = 0
|
AND suspicious = 0
|
||||||
|
AND troll = 0
|
||||||
`)) {
|
`)) {
|
||||||
answers = JSON.parse(answers);
|
answers = JSON.parse(answers);
|
||||||
writins = JSON.parse(writins);
|
writins = JSON.parse(writins);
|
||||||
|
@ -113,4 +142,36 @@ router.get('/census/export', handleErrorAsync(async (req, res) => {
|
||||||
return res.set('content-type', 'text/csv').send(Papa.unparse(report));
|
return res.set('content-type', 'text/csv').send(Papa.unparse(report));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
router.get('/census/moderation/queue', handleErrorAsync(async (req, res) => {
|
||||||
|
if (!req.isGranted('census')) {
|
||||||
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = await req.db.all(SQL`
|
||||||
|
SELECT id, answers, writins FROM census
|
||||||
|
WHERE locale = ${global.config.locale}
|
||||||
|
AND edition = ${global.config.census.edition}
|
||||||
|
AND (answers LIKE '{"0":"osobą niebinarną"%' OR answers LIKE '{"0":"nie wiem"%') -- TODO polish-specific
|
||||||
|
AND troll IS NULL
|
||||||
|
ORDER BY RANDOM()
|
||||||
|
`);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
count: queue.length,
|
||||||
|
next: queue.length ? queue[0] : null,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.post('/census/moderation/decide', handleErrorAsync(async (req, res) => {
|
||||||
|
if (!req.isGranted('census')) {
|
||||||
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = await req.db.get(SQL`
|
||||||
|
UPDATE census SET troll = ${parseInt(req.body.decision)} WHERE id = ${req.body.id}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return res.json('ok');
|
||||||
|
}));
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
Reference in New Issue