[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}.
|
||||
prev: 'Poprzednie 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.'
|
||||
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'
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
-- Up
|
||||
|
||||
ALTER TABLE census ADD COLUMN troll INTEGER NULL;
|
||||
|
||||
-- Down
|
||||
|
|
@ -264,6 +264,7 @@ export default {
|
|||
|
||||
if (config.census.enabled) {
|
||||
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) {
|
||||
|
|
|
@ -10,12 +10,16 @@
|
|||
<template v-if="q === null">
|
||||
<section v-if="$isGranted('census')">
|
||||
<div class="alert alert-info">
|
||||
{{countResponses}}
|
||||
<T>census.replies</T>
|
||||
|
||||
<a href="/api/census/export" class="btn btn-outline-secondary btn-sm float-end">
|
||||
<Icon v="download"/>
|
||||
</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>
|
||||
</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) => {
|
||||
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
|
||||
WHERE locale = ${global.config.locale}
|
||||
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) => {
|
||||
|
@ -82,11 +110,12 @@ router.get('/census/export', handleErrorAsync(async (req, res) => {
|
|||
}
|
||||
|
||||
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
|
||||
WHERE locale = ${global.config.locale}
|
||||
AND edition = ${global.config.census.edition}
|
||||
AND suspicious = 0
|
||||
AND troll = 0
|
||||
`)) {
|
||||
answers = JSON.parse(answers);
|
||||
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));
|
||||
}));
|
||||
|
||||
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;
|
||||
|
|
Reference in New Issue