#174 better banning
This commit is contained in:
parent
35fb535955
commit
fb689e2f6c
|
@ -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="/">
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}:'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}'
|
||||
|
|
|
@ -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: 'אַבראָסעקסועל'
|
||||
|
|
|
@ -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: '嫩浪漫傾向'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
-- Up
|
||||
|
||||
ALTER TABLE users ADD COLUMN bannedReason TEXT NULL;
|
||||
|
||||
-- Down
|
|
@ -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,7 +157,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { head } from "../src/helpers";
|
||||
import {head, listToDict} from "../src/helpers";
|
||||
import { pronouns } from "~/src/data";
|
||||
import { buildPronoun } from "../src/buildPronoun";
|
||||
|
||||
|
@ -147,6 +167,7 @@
|
|||
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}`,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
Reference in New Issue