#174 better banning

This commit is contained in:
Avris 2021-06-16 16:08:38 +02:00
parent 35fb535955
commit fb689e2f6c
16 changed files with 182 additions and 11 deletions

View File

@ -1,5 +1,6 @@
<template>
<header v-if="config.header" class="mb-4">
<div v-if="config.header" class="mb-4">
<header>
<div class="d-none d-lg-flex justify-content-between align-items-center flex-row nav-custom btn-group mb-0">
<nuxt-link v-for="link in links" :key="link.link" :to="link.link" :class="`nav-item btn btn-sm ${isActiveRoute(link) ? 'active' : ''} ${link.header ? 'flex-grow-0' : ''}`">
<h1 v-if="link.header" class="text-nowrap">
@ -48,18 +49,39 @@
</div>
</div>
</div>
<div v-if="locales[config.locale].published === false" class="alert alert-warning mt-3">
</header>
<div v-if="locales[config.locale].published === false" class="alert alert-warning mb-0">
<Icon v="exclamation-triangle"/>
This language version is still under construction!
</div>
<div v-show="showCensus" class="alert alert-info mt-3">
<div v-show="showCensus" class="alert alert-info mb-0">
<a href="#" class="float-end" @click.prevent="dismissCensus">
<Icon v="times"/>
</a>
<Icon v="user-chart" size="2" class="d-inline-block float-start me-3 mt-2"/>
<T silent>census.banner</T>
</div>
</header>
<div v-if="$user() && $user().bannedReason" class="alert alert-danger mb-0 container">
<p class="h4 mb-2">
<Icon v="ban"/>
<T>ban.header</T>
</p>
<p >
<T>ban.reason</T>:
{{$user().bannedReason}}
</p>
<p>
<T>ban.termsIntro</T>
</p>
<blockquote class="small">
It is forbidden to post on the Service any Content that might break the law or violate social norms,
including but not limited to:
propagation of totalitarian regimes, hate speech, racism, xenophobia, homophobia, transphobia, queerphobia,
misogyny, harassment, impersonation, child pornography, unlawful conduct, misinformation,
sharing of someone else's personal data, spam, advertisement, copyright or trademark violations.
</blockquote>
</div>
</div>
<header v-else class="mb-4">
<h1 class="text-nowrap">
<nuxt-link to="/">

View File

@ -496,6 +496,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terms=Terms of Service}:'
flags:
Abrosexual: 'Abrosexuell'
Achillean: 'Achillean'

View File

@ -571,3 +571,11 @@ error:
mode:
dark: 'Dark mode'
light: 'Light mode'
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terms=Terms of Service}:'

View File

@ -506,6 +506,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terinos=Terms of Service}:'
flags:
Abroromantic: 'Abrorrománti{inflection_c}'
Abrosexual: 'Abrosexual'

View File

@ -487,6 +487,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terms=Terms of Service}:'
flags:
Abroromantic: 'Abroromantyczn{adjective_n}'
Abrosexual: 'Abroseksualn{adjective_n}'

View File

@ -500,6 +500,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/voorwaarden=Terms of Service}:'
flags:
Abroromantic: 'Abroromantisch'
Abrosexual: 'Abroseksueel'

View File

@ -1105,6 +1105,14 @@ mode:
dark: 'Tryb nocny'
light: 'Tryb dzienny'
ban:
reason: 'Powód blokady'
action: 'Zablokuj tę osobę'
confirm: 'Czy na pewno chcesz zbanować @%username%?'
header: 'Twoje konto jest zablokowane. Twoje profile nie są widoczne publicznie.'
banned: 'Zbanowanx'
termsIntro: 'Zgodnie z naszym {/regulamin=Regulaminem}:'
flags:
Abroromantic: 'Abroromantyczn{adjective_n}'
Abrosexual: 'Abroseksualn{adjective_n}'

View File

@ -504,6 +504,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/termos=Terms of Service}:'
flags:
Abroromantic: 'Abrorromânti{inflection_c}'
Abrosexual: 'Abrossexual'

View File

@ -1076,6 +1076,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terinos=Terms of Service}:'
flags:
Abroromantic: 'Abroromantyczn{adjective_n}'
Abrosexual: 'Abroseksualn{adjective_n}'

View File

@ -517,6 +517,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terinos=Terms of Service}:'
flags:
Abroromantic: 'אַבראָראָמאַנטיש'
Abrosexual: 'אַבראָסעקסועל'

View File

@ -485,6 +485,15 @@ mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
# TODO
ban:
reason: 'Ban reason'
action: 'Ban this person'
confirm: 'Are you sure you want to ban @%username%?'
header: 'You''re banned. Your profile will not be shown to anyone.'
banned: 'Banned'
termsIntro: 'According to our {/terinos=Terms of Service}:'
flags:
Abrosexual: '嫩性戀'
Abroromantic: '嫩浪漫傾向'

5
migrations/027-ban.sql Normal file
View File

@ -0,0 +1,5 @@
-- Up
ALTER TABLE users ADD COLUMN bannedReason TEXT NULL;
-- Down

View File

@ -31,6 +31,16 @@
</div>
</div>
<section v-if="$isGranted('users') && profile.bannedReason">
<div class="alert alert-warning">
<p class="h4">
<Icon v="ban"/>
{{$t('ban.banned')}}
</p>
<p class="mb-0">{{profile.bannedReason}}</p>
</div>
</section>
<section v-if="profile.age ||profile.description.trim().length">
<p v-for="line in profile.description.split('\n')" class="mb-1">
<Spelling escape :text="line"/>
@ -111,6 +121,16 @@
<OpinionLegend/>
</section>
<section v-if="$isGranted('users')">
<div class="alert alert-warning">
<textarea v-model="profile.bannedReason" class="form-control" rows="3" :placeholder="$t('ban.reason')" :disabled="saving"></textarea>
<button class="btn btn-danger d-block w-100 mt-2" :disabled="saving" @click="ban">
<Icon v="ban"/>
{{$t('ban.action')}}
</button>
</div>
</section>
<Separator icon="heart"/>
<Support/>
<section>
@ -137,16 +157,17 @@
</template>
<script>
import { head } from "../src/helpers";
import {head, listToDict} from "../src/helpers";
import { pronouns } from "~/src/data";
import { buildPronoun } from "../src/buildPronoun";
export default {
data() {
return {
profiles: {},
glue: ' ' + this.$t('pronouns.or') + ' ',
allFlags: process.env.FLAGS,
profiles: {},
glue: ' ' + this.$t('pronouns.or') + ' ',
allFlags: process.env.FLAGS,
saving: false,
}
},
async asyncData({ app, route }) {
@ -238,6 +259,20 @@
return mainPronoun;
},
},
methods: {
async ban() {
await this.$confirm(this.$t('ban.confirm', {username: this.username}), 'danger');
this.saving = true;
try {
await this.$post(`/admin/ban/${encodeURIComponent(this.username)}`, {
reason: this.profile.bannedReason,
});
window.location.reload();
} finally {
this.saving = false;
}
}
},
head() {
return head({
title: `@${this.username}`,

View File

@ -117,4 +117,20 @@ router.get('/admin/stats', handleErrorAsync(async (req, res) => {
return res.json(stats);
}));
const normalise = s => s.trim().toLowerCase();
router.post('/admin/ban/:username', handleErrorAsync(async (req, res) => {
if (!req.isGranted('users')) {
return res.status(401).json({error: 'Unauthorised'});
}
await req.db.get(SQL`
UPDATE users
SET bannedReason = ${req.body.reason || null}
WHERE lower(trim(replace(replace(replace(replace(replace(replace(replace(replace(replace(username, 'Ą', 'ą'), 'Ć', 'ć'), 'Ę', 'ę'), 'Ł', 'ł'), 'Ń', 'ń'), 'Ó', 'ó'), 'Ś', 'ś'), 'Ż', 'ż'), 'Ź', 'ż'))) = ${normalise(req.params.username)}
`);
return res.json(true);
}));
export default router;

View File

@ -24,9 +24,9 @@ const calcAge = birthday => {
return parseInt(Math.floor(diff / 1000 / 60 / 60 / 24 / 365.25));
}
const fetchProfiles = async (db, username, self) => {
const fetchProfiles = async (db, username, self, isAdmin) => {
const profiles = await db.all(SQL`
SELECT profiles.*, users.id, users.username, users.email, users.avatarSource FROM profiles LEFT JOIN users on users.id == profiles.userId
SELECT profiles.*, users.id, users.username, users.email, users.avatarSource, users.bannedReason FROM profiles LEFT JOIN users on users.id == profiles.userId
WHERE lower(trim(replace(replace(replace(replace(replace(replace(replace(replace(replace(username, 'Ą', 'ą'), 'Ć', 'ć'), 'Ę', 'ę'), 'Ł', 'ł'), 'Ń', 'ń'), 'Ó', 'ó'), 'Ś', 'ś'), 'Ż', 'ż'), 'Ź', 'ż'))) = ${normalise(username)}
AND profiles.active = 1
ORDER BY profiles.locale
@ -34,6 +34,9 @@ const fetchProfiles = async (db, username, self) => {
const p = {}
for (let profile of profiles) {
if (profile.bannedReason !== null && !isAdmin && !self) {
return {};
}
p[profile.locale] = {
id: profile.id,
userId: profile.userId,
@ -52,6 +55,7 @@ const fetchProfiles = async (db, username, self) => {
teamName: profile.teamName,
footerName: profile.footerName,
footerAreas: profile.footerAreas ? profile.footerAreas.split(',') : [],
bannedReason: profile.bannedReason,
};
}
return p;
@ -60,7 +64,7 @@ const fetchProfiles = async (db, username, self) => {
const router = Router();
router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
return res.json(await fetchProfiles(req.db, req.params.username, req.user && req.user.username === req.params.username))
return res.json(await fetchProfiles(req.db, req.params.username, req.user && req.user.username === req.params.username, req.isGranted('users')))
}));
router.post('/profile/save', handleErrorAsync(async (req, res) => {

View File

@ -157,6 +157,7 @@ const reloadUser = async (req, res, next) => {
|| req.user.email !== dbUser.email
|| req.user.roles !== dbUser.roles
|| req.user.avatarSource !== dbUser.avatarSource
|| req.user.bannedReason !== dbUser.bannedReason
) {
const newUser = {
...dbUser,