#174 better banning
This commit is contained in:
parent
35fb535955
commit
fb689e2f6c
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<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">
|
<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' : ''}`">
|
<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">
|
<h1 v-if="link.header" class="text-nowrap">
|
||||||
|
@ -48,18 +49,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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"/>
|
<Icon v="exclamation-triangle"/>
|
||||||
This language version is still under construction!
|
This language version is still under construction!
|
||||||
</div>
|
</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">
|
<a href="#" class="float-end" @click.prevent="dismissCensus">
|
||||||
<Icon v="times"/>
|
<Icon v="times"/>
|
||||||
</a>
|
</a>
|
||||||
<Icon v="user-chart" size="2" class="d-inline-block float-start me-3 mt-2"/>
|
<Icon v="user-chart" size="2" class="d-inline-block float-start me-3 mt-2"/>
|
||||||
<T silent>census.banner</T>
|
<T silent>census.banner</T>
|
||||||
</div>
|
</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">
|
<header v-else class="mb-4">
|
||||||
<h1 class="text-nowrap">
|
<h1 class="text-nowrap">
|
||||||
<nuxt-link to="/">
|
<nuxt-link to="/">
|
||||||
|
|
|
@ -496,6 +496,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abrosexual: 'Abrosexuell'
|
Abrosexual: 'Abrosexuell'
|
||||||
Achillean: 'Achillean'
|
Achillean: 'Achillean'
|
||||||
|
|
|
@ -571,3 +571,11 @@ error:
|
||||||
mode:
|
mode:
|
||||||
dark: 'Dark mode'
|
dark: 'Dark mode'
|
||||||
light: 'Light 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
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abrorrománti{inflection_c}'
|
Abroromantic: 'Abrorrománti{inflection_c}'
|
||||||
Abrosexual: 'Abrosexual'
|
Abrosexual: 'Abrosexual'
|
||||||
|
|
|
@ -487,6 +487,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||||
|
|
|
@ -500,6 +500,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abroromantisch'
|
Abroromantic: 'Abroromantisch'
|
||||||
Abrosexual: 'Abroseksueel'
|
Abrosexual: 'Abroseksueel'
|
||||||
|
|
|
@ -1105,6 +1105,14 @@ mode:
|
||||||
dark: 'Tryb nocny'
|
dark: 'Tryb nocny'
|
||||||
light: 'Tryb dzienny'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||||
|
|
|
@ -504,6 +504,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abrorromânti{inflection_c}'
|
Abroromantic: 'Abrorromânti{inflection_c}'
|
||||||
Abrosexual: 'Abrossexual'
|
Abrosexual: 'Abrossexual'
|
||||||
|
|
|
@ -1076,6 +1076,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||||
|
|
|
@ -517,6 +517,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abroromantic: 'אַבראָראָמאַנטיש'
|
Abroromantic: 'אַבראָראָמאַנטיש'
|
||||||
Abrosexual: 'אַבראָסעקסועל'
|
Abrosexual: 'אַבראָסעקסועל'
|
||||||
|
|
|
@ -485,6 +485,15 @@ mode:
|
||||||
dark: 'Dark mode' # TODO
|
dark: 'Dark mode' # TODO
|
||||||
light: 'Light mode'
|
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:
|
flags:
|
||||||
Abrosexual: '嫩性戀'
|
Abrosexual: '嫩性戀'
|
||||||
Abroromantic: '嫩浪漫傾向'
|
Abroromantic: '嫩浪漫傾向'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- Up
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN bannedReason TEXT NULL;
|
||||||
|
|
||||||
|
-- Down
|
|
@ -31,6 +31,16 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<section v-if="profile.age ||profile.description.trim().length">
|
||||||
<p v-for="line in profile.description.split('\n')" class="mb-1">
|
<p v-for="line in profile.description.split('\n')" class="mb-1">
|
||||||
<Spelling escape :text="line"/>
|
<Spelling escape :text="line"/>
|
||||||
|
@ -111,6 +121,16 @@
|
||||||
<OpinionLegend/>
|
<OpinionLegend/>
|
||||||
</section>
|
</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"/>
|
<Separator icon="heart"/>
|
||||||
<Support/>
|
<Support/>
|
||||||
<section>
|
<section>
|
||||||
|
@ -137,16 +157,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { head } from "../src/helpers";
|
import {head, listToDict} from "../src/helpers";
|
||||||
import { pronouns } from "~/src/data";
|
import { pronouns } from "~/src/data";
|
||||||
import { buildPronoun } from "../src/buildPronoun";
|
import { buildPronoun } from "../src/buildPronoun";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
profiles: {},
|
profiles: {},
|
||||||
glue: ' ' + this.$t('pronouns.or') + ' ',
|
glue: ' ' + this.$t('pronouns.or') + ' ',
|
||||||
allFlags: process.env.FLAGS,
|
allFlags: process.env.FLAGS,
|
||||||
|
saving: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async asyncData({ app, route }) {
|
async asyncData({ app, route }) {
|
||||||
|
@ -238,6 +259,20 @@
|
||||||
return mainPronoun;
|
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() {
|
head() {
|
||||||
return head({
|
return head({
|
||||||
title: `@${this.username}`,
|
title: `@${this.username}`,
|
||||||
|
|
|
@ -117,4 +117,20 @@ router.get('/admin/stats', handleErrorAsync(async (req, res) => {
|
||||||
return res.json(stats);
|
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;
|
export default router;
|
||||||
|
|
|
@ -24,9 +24,9 @@ const calcAge = birthday => {
|
||||||
return parseInt(Math.floor(diff / 1000 / 60 / 60 / 24 / 365.25));
|
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`
|
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)}
|
WHERE lower(trim(replace(replace(replace(replace(replace(replace(replace(replace(replace(username, 'Ą', 'ą'), 'Ć', 'ć'), 'Ę', 'ę'), 'Ł', 'ł'), 'Ń', 'ń'), 'Ó', 'ó'), 'Ś', 'ś'), 'Ż', 'ż'), 'Ź', 'ż'))) = ${normalise(username)}
|
||||||
AND profiles.active = 1
|
AND profiles.active = 1
|
||||||
ORDER BY profiles.locale
|
ORDER BY profiles.locale
|
||||||
|
@ -34,6 +34,9 @@ const fetchProfiles = async (db, username, self) => {
|
||||||
|
|
||||||
const p = {}
|
const p = {}
|
||||||
for (let profile of profiles) {
|
for (let profile of profiles) {
|
||||||
|
if (profile.bannedReason !== null && !isAdmin && !self) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
p[profile.locale] = {
|
p[profile.locale] = {
|
||||||
id: profile.id,
|
id: profile.id,
|
||||||
userId: profile.userId,
|
userId: profile.userId,
|
||||||
|
@ -52,6 +55,7 @@ const fetchProfiles = async (db, username, self) => {
|
||||||
teamName: profile.teamName,
|
teamName: profile.teamName,
|
||||||
footerName: profile.footerName,
|
footerName: profile.footerName,
|
||||||
footerAreas: profile.footerAreas ? profile.footerAreas.split(',') : [],
|
footerAreas: profile.footerAreas ? profile.footerAreas.split(',') : [],
|
||||||
|
bannedReason: profile.bannedReason,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
|
@ -60,7 +64,7 @@ const fetchProfiles = async (db, username, self) => {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
|
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) => {
|
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.email !== dbUser.email
|
||||||
|| req.user.roles !== dbUser.roles
|
|| req.user.roles !== dbUser.roles
|
||||||
|| req.user.avatarSource !== dbUser.avatarSource
|
|| req.user.avatarSource !== dbUser.avatarSource
|
||||||
|
|| req.user.bannedReason !== dbUser.bannedReason
|
||||||
) {
|
) {
|
||||||
const newUser = {
|
const newUser = {
|
||||||
...dbUser,
|
...dbUser,
|
||||||
|
|
Reference in New Issue