#219 reporting profiles
This commit is contained in:
parent
617cf70641
commit
c45a8c0bf6
|
@ -83,7 +83,7 @@
|
|||
</template>
|
||||
<ul v-if="profiles !== undefined" class="list-group">
|
||||
<li v-for="(options, locale) in locales" :key="locale" :class="['list-group-item', locale === config.locale ? 'profile-current' : '']">
|
||||
<ProfileOverview :profile="profiles[locale]" :locale="locale" @update="setProfiles"/>
|
||||
<ProfileOverview :username="username" :profile="profiles[locale]" :locale="locale" @update="setProfiles"/>
|
||||
</li>
|
||||
</ul>
|
||||
</Loading>
|
||||
|
|
|
@ -1,15 +1,35 @@
|
|||
<template>
|
||||
<client-only v-if="config.profile.editorEnabled">
|
||||
<div v-if="config.profile.editorEnabled">
|
||||
<section v-if="$user()">
|
||||
<a v-if="!showReportForm" href="#" @click.prevent="showReportForm = true" class="small">
|
||||
<Icon v="spider"/>
|
||||
<T>report.action</T>
|
||||
</a>
|
||||
<div v-else-if="!reported">
|
||||
<textarea v-model="reportComment" class="form-control" rows="3" :placeholder="$t('report.comment')" :disabled="saving" required></textarea>
|
||||
<button class="btn btn-danger d-block w-100 mt-2" :disabled="saving || !reportComment" @click="report">
|
||||
<Icon v="spider"/>
|
||||
<T>report.action</T>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="alert alert-success">
|
||||
<T>report.sent</T>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="$isGranted('users')">
|
||||
<div class="alert alert-warning">
|
||||
<a v-if="!showBanForm" href="#" @click.prevent="showBanForm = true" class="small">
|
||||
<Icon v="ban"/>
|
||||
<T>ban.action</T>
|
||||
</a>
|
||||
<div v-else>
|
||||
<textarea v-model="user.bannedReason" class="form-control" rows="3" :placeholder="$t('ban.reason') + ' ' + $t('ban.visible')" :disabled="saving"></textarea>
|
||||
<button class="btn btn-danger d-block w-100 mt-2" :disabled="saving" @click="ban">
|
||||
<Icon v="ban"/>
|
||||
{{$t('ban.action')}}
|
||||
<T>ban.action</T>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -22,6 +42,12 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
showReportForm: false,
|
||||
reportComment: '',
|
||||
reported: false,
|
||||
|
||||
showBanForm: !!this.user.bannedReason,
|
||||
|
||||
saving: false,
|
||||
}
|
||||
},
|
||||
|
@ -37,7 +63,20 @@
|
|||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
async report() {
|
||||
if (!this.reportComment) { return; }
|
||||
await this.$confirm(this.$t('report.confirm', {username: this.user.username}), 'danger');
|
||||
this.saving = true;
|
||||
try {
|
||||
await this.$post(`/profile/report/${encodeURIComponent(this.user.username)}`, {
|
||||
comment: this.reportComment,
|
||||
});
|
||||
this.reported = true;
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<Table :data="visibleNouns()" columns="3" :marked="(el) => !el.approved" fixed ref="dictionarytable">
|
||||
<Table :data="visibleNouns()" :columns="3" :marked="(el) => !el.approved" fixed ref="dictionarytable">
|
||||
<template v-slot:header>
|
||||
<th class="text-nowrap">
|
||||
<Icon v="mars"/>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<Table :data="visibleEntries()" columns="3" :marked="(el) => !el.approved" fixed ref="dictionarytable">
|
||||
<Table :data="visibleEntries()" :columns="3" :marked="(el) => !el.approved" fixed ref="dictionarytable">
|
||||
<template v-slot:header>
|
||||
<th class="text-nowrap">
|
||||
<Icon v="comment-times"/>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="d-flex justify-content-between align-items-center">
|
||||
{{ locales[locale].name }}
|
||||
<span v-if="profile">
|
||||
<LocaleLink :locale="locale" :link="`/@${profile.username}`" class="badge bg-primary text-white text-white">
|
||||
<LocaleLink :locale="locale" :link="`/@${username}`" class="badge bg-primary text-white text-white">
|
||||
<Icon v="id-card"/>
|
||||
<T>profile.show</T>
|
||||
</LocaleLink>
|
||||
|
@ -27,6 +27,7 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
username: { required: true },
|
||||
profile: { required: true },
|
||||
locale: { required: true },
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<table :class="['table table-striped table-hover', fixed ? 'table-fixed-' + columns : '']">
|
||||
<thead ref="thead">
|
||||
<tr>
|
||||
<td :colspan="columns + 1">
|
||||
<td :colspan="columns">
|
||||
<nav v-if="pages > 1">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0">
|
||||
<li v-for="p in pagesRange" :class="['page-item', p.page === page ? 'active' : '', p.enabled ? '' : 'disabled']">
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<Table :data="visibleEntries()" columns="1" fixed :marked="(el) => !el.approved" ref="dictionarytable">
|
||||
<Table :data="visibleEntries()" :columns="1" fixed :marked="(el) => !el.approved" ref="dictionarytable">
|
||||
<template v-slot:header>
|
||||
<th class="cell-wide"></th>
|
||||
<th></th>
|
||||
|
|
|
@ -511,6 +511,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/bedingungen=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abrosexual: 'Abrosexuell'
|
||||
Achillean: 'Achillean'
|
||||
|
|
|
@ -608,3 +608,9 @@ ban:
|
|||
header: 'You''re banned. Your profile will not be shown to anyone.'
|
||||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terms=Terms of Service}:'
|
||||
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
|
|
@ -523,6 +523,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terinos=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abrorrománti{inflection_c}'
|
||||
Abrosexual: 'Abrosexual'
|
||||
|
|
|
@ -511,6 +511,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terms=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||
|
|
|
@ -518,6 +518,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/voorwaarden=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abroromantisch'
|
||||
Abrosexual: 'Abroseksueel'
|
||||
|
|
|
@ -1123,6 +1123,12 @@ ban:
|
|||
banned: 'Zbanowanx'
|
||||
termsIntro: 'Zgodnie z naszym {/regulamin=Regulaminem}:'
|
||||
|
||||
report:
|
||||
action: 'Zgłoś profil'
|
||||
comment: 'Opisz krótko, w czym problem'
|
||||
confirm: 'Czy na pewno chcesz zgłosić profil @%username%?'
|
||||
sent: 'Twoje zgłoszenie zostało wysłane. Dziękujemy za pomoc!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||
|
|
|
@ -521,6 +521,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/termos=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abrorromânti{inflection_c}'
|
||||
Abrosexual: 'Abrossexual'
|
||||
|
|
|
@ -1094,6 +1094,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terinos=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'Abroromantyczn{adjective_n}'
|
||||
Abrosexual: 'Abroseksualn{adjective_n}'
|
||||
|
|
|
@ -535,6 +535,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terinos=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abroromantic: 'אַבראָראָמאַנטיש'
|
||||
Abrosexual: 'אַבראָסעקסועל'
|
||||
|
|
|
@ -503,6 +503,13 @@ ban:
|
|||
banned: 'Banned'
|
||||
termsIntro: 'According to our {/terinos=Terms of Service}:'
|
||||
|
||||
# TODO
|
||||
report:
|
||||
action: 'Report abuse'
|
||||
comment: 'Please explain briefly what''s wrong with this profile'
|
||||
confirm: 'Are you sure you want to report @%username%?'
|
||||
sent: 'Your report has been sent. Thanks for your help!'
|
||||
|
||||
flags:
|
||||
Abrosexual: '嫩性戀'
|
||||
Abroromantic: '嫩浪漫傾向'
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
-- Up
|
||||
|
||||
CREATE TABLE reports (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
userId TEXT NOT NULL,
|
||||
reporterId TEXT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
isAutomatic INTEGER,
|
||||
isHandled INTEGER,
|
||||
|
||||
FOREIGN KEY(userId) REFERENCES users(id),
|
||||
FOREIGN KEY(reporterId) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE INDEX "reports_isAutomatic" ON "reports" ("isAutomatic");
|
||||
CREATE INDEX "reports_isHandled" ON "reports" ("isHandled");
|
||||
|
||||
-- Down
|
||||
|
||||
DROP TABLE reports;
|
|
@ -3,6 +3,7 @@ import t from '../src/translator';
|
|||
import config from '../data/config.suml';
|
||||
import {buildDict} from "../src/helpers";
|
||||
import {DateTime} from "luxon";
|
||||
import {decodeTime} from 'ulid';
|
||||
|
||||
export default ({ app, store }) => {
|
||||
Vue.prototype.$eventHub = new Vue();
|
||||
|
@ -47,4 +48,8 @@ export default ({ app, store }) => {
|
|||
const dt = DateTime.fromSeconds(timestamp);
|
||||
return dt.toFormat('y-MM-dd HH:mm')
|
||||
}
|
||||
|
||||
Vue.prototype.$ulidTime = (ulid) => {
|
||||
return decodeTime(ulid) / 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,33 +90,53 @@
|
|||
{{ stats.cards * 100 }}%
|
||||
</section>
|
||||
|
||||
<section v-if="$isGranted('users') && suspiciousUsers.length > 0">
|
||||
<section v-if="$isGranted('users')">
|
||||
<h3>
|
||||
<Icon v="siren-on"/>
|
||||
Suspicious accounts
|
||||
Abuse reports
|
||||
</h3>
|
||||
<Table :data="suspiciousUsers" columns="2">
|
||||
<Table :data="abuseReports" :columns="4">
|
||||
<template v-slot:header>
|
||||
<th class="text-nowrap">
|
||||
Suspicious account
|
||||
</th>
|
||||
<th class="text-nowrap">
|
||||
Reported by
|
||||
</th>
|
||||
<th class="text-nowrap">
|
||||
Comment
|
||||
</th>
|
||||
<th class="text-nowrap">
|
||||
Action
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<template v-slot:row="s"><template v-if="s">
|
||||
<td>
|
||||
<LocaleLink :link="`/@${s.el.username}`" :locale="s.el.locale">
|
||||
{{s.el.username}}
|
||||
<span class="badge bg-light text-dark">{{s.el.locale}}</span>
|
||||
</LocaleLink>
|
||||
<a :href="`https://pronouns.page/${s.el.susUsername}`" target="_blank" rel="noopener">@{{s.el.susUsername}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="badge bg-light text-success border border-success float-end"
|
||||
@click.prevent="checkedSuspicious(s.el.id)"
|
||||
<span v-if="s.el.isAutomatic" class="badge bg-info">
|
||||
Keyword found
|
||||
</span>
|
||||
<a v-else :href="`https://pronouns.page/${s.el.reporterUsername}`" target="_blank" rel="noopener">@{{s.el.reporterUsername}}</a>
|
||||
<small>({{$datetime($ulidTime(s.el.id))}})</small>
|
||||
</td>
|
||||
<td class="small">
|
||||
{{ s.el.comment }}
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="s.el.isHandled" class="badge bg-success">
|
||||
Case closed
|
||||
</span>
|
||||
<a v-else href="#" class="badge bg-light text-success border border-success"
|
||||
@click.prevent="handleReport(s.el.id)"
|
||||
>
|
||||
<Icon v="thumbs-up"/>
|
||||
I checked the profile, it's OK.
|
||||
</a>
|
||||
</td>
|
||||
</template></template>
|
||||
|
||||
<template v-slot:empty>
|
||||
<Icon v="search"/>
|
||||
<T>nouns.empty</T>
|
||||
</template>
|
||||
</Table>
|
||||
</section>
|
||||
|
||||
|
@ -190,14 +210,14 @@
|
|||
stats = await app.$axios.$get(`/admin/stats`);
|
||||
} catch {}
|
||||
|
||||
let suspiciousUsers = [];
|
||||
let abuseReports = [];
|
||||
try {
|
||||
suspiciousUsers = await app.$axios.$get(`/admin/suspicious`);
|
||||
abuseReports = await app.$axios.$get(`/admin/reports`);
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
stats,
|
||||
suspiciousUsers,
|
||||
abuseReports,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -206,10 +226,13 @@
|
|||
this.users = await this.$axios.$get(`/admin/users`);
|
||||
}
|
||||
},
|
||||
async checkedSuspicious(id) {
|
||||
await this.$confirm('Are you sure you want to mark this profile as not suspicious?', 'success');
|
||||
await this.$post(`/admin/suspicious/checked/${id}`);
|
||||
this.suspiciousUsers = this.suspiciousUsers.filter(u => u.id !== id);
|
||||
async handleReport(id) {
|
||||
await this.$confirm('Are you sure you want to mark this report as handled?', 'success');
|
||||
await this.$post(`/admin/reports/handle/${id}`);
|
||||
this.abuseReports = this.abuseReports.map(r => {
|
||||
if (r.id === id) { r.isHandled = true; }
|
||||
return r;
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -18,16 +18,16 @@
|
|||
{{options.name}}
|
||||
</LocaleLink>
|
||||
</div>
|
||||
<div v-if="$user() && $user().username === profile.username">
|
||||
<div v-if="$user() && $user().username === user.username">
|
||||
<nuxt-link to="/editor" class="btn btn-primary btn-sm mb-2 mx-1">
|
||||
<Icon v="edit"/>
|
||||
<T>profile.edit</T>
|
||||
</nuxt-link>
|
||||
<a :href="`https://pronouns.page/@${profile.username}`" v-if="Object.keys(user.profiles).length > 1"
|
||||
<a :href="`https://pronouns.page/@${user.username}`" v-if="Object.keys(user.profiles).length > 1"
|
||||
class="btn btn-outline-secondary btn-sm mb-2 mx-1"
|
||||
>
|
||||
<Icon v="external-link"/>
|
||||
pronouns.page/@{{profile.username}}
|
||||
pronouns.page/@{{user.username}}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="($user() && $user().username === profile.username) || $isGranted('users')">
|
||||
|
|
|
@ -139,50 +139,28 @@ router.post('/admin/ban/:username', handleErrorAsync(async (req, res) => {
|
|||
return res.json(true);
|
||||
}));
|
||||
|
||||
router.get('/admin/suspicious', handleErrorAsync(async (req, res) => {
|
||||
router.get('/admin/reports', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('users')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
return res.json(await req.db.all(SQL`
|
||||
SELECT users.id, users.username, profiles.locale FROM profiles
|
||||
LEFT JOIN users ON profiles.userId = users.id
|
||||
WHERE users.suspiciousChecked != 1
|
||||
AND users.bannedReason IS NULL
|
||||
AND (
|
||||
lower(customFlags) LIKE '%superstr%'
|
||||
OR lower(description) LIKE '%superstr%'
|
||||
OR lower(customFlags) LIKE '%superhet%'
|
||||
OR lower(description) LIKE '%superhet%'
|
||||
OR lower(customFlags) LIKE '%super-%'
|
||||
OR lower(description) LIKE '%super-%'
|
||||
OR lower(customFlags) LIKE '%phobe%'
|
||||
OR lower(description) LIKE '%phobe%'
|
||||
OR lower(customFlags) LIKE '%phobic%'
|
||||
OR lower(description) LIKE '%phobic%'
|
||||
OR lower(customFlags) LIKE '%terf%'
|
||||
OR lower(description) LIKE '%terf%'
|
||||
OR lower(customFlags) LIKE '%radfem%'
|
||||
OR lower(description) LIKE '%radfem%'
|
||||
OR lower(customFlags) LIKE '%gender critical%'
|
||||
OR lower(description) LIKE '%gender critical%'
|
||||
OR lower(customFlags) LIKE '%helicopter%'
|
||||
OR lower(description) LIKE '%helicopter%'
|
||||
OR lower(pronouns) LIKE '%helicopter%'
|
||||
OR lower(pronouns) LIKE '%nor/mal%'
|
||||
)
|
||||
ORDER BY users.id DESC
|
||||
SELECT reports.id, sus.username AS susUsername, reporter.username AS reporterUsername, reports.comment, reports.isAutomatic, reports.isHandled
|
||||
FROM reports
|
||||
LEFT JOIN users sus ON reports.userId = sus.id
|
||||
LEFT JOIN users reporter ON reports.reporterId = reporter.id
|
||||
ORDER BY reports.isHandled ASC, reports.id ASC
|
||||
`));
|
||||
}));
|
||||
|
||||
router.post('/admin/suspicious/checked/:id', handleErrorAsync(async (req, res) => {
|
||||
router.post('/admin/reports/handle/:id', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('users')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
await req.db.get(SQL`
|
||||
UPDATE users
|
||||
SET suspiciousChecked = 1
|
||||
UPDATE reports
|
||||
SET isHandled = 1
|
||||
WHERE id=${req.params.id}
|
||||
`);
|
||||
|
||||
|
|
|
@ -53,6 +53,46 @@ const fetchProfiles = async (db, username, self, isAdmin) => {
|
|||
return p;
|
||||
};
|
||||
|
||||
function* isSuspicious(profile) {
|
||||
const description = profile.description.toLowerCase();
|
||||
const flags = JSON.stringify(profile.customFlags).toLowerCase();
|
||||
const pronouns = JSON.stringify(profile.pronouns).toLowerCase();
|
||||
|
||||
if (description.includes('superstr') || description.includes('superhet') || description.includes('super-') ||
|
||||
flags.includes('superstr') || flags.includes('superhet') || flags.includes('super-')
|
||||
) {
|
||||
yield 'Superstraight';
|
||||
}
|
||||
|
||||
if (description.includes('phobe') || description.includes('phobic') ||
|
||||
flags.includes('phobe') || flags.includes('phobic')
|
||||
) {
|
||||
yield '-phobic';
|
||||
}
|
||||
|
||||
if (description.includes('terf') || description.includes('radfem') || description.includes('gender critical') ||
|
||||
flags.includes('terf') || flags.includes('radfem') || flags.includes('gender critical')
|
||||
) {
|
||||
yield 'TERF';
|
||||
}
|
||||
|
||||
if (description.includes('helicopter') ||
|
||||
flags.includes('helicopter') ||
|
||||
pronouns.includes('helicopter')
|
||||
) {
|
||||
yield 'Helicopter';
|
||||
}
|
||||
|
||||
if (pronouns.includes('nor/mal')
|
||||
) {
|
||||
yield 'nor/mal';
|
||||
}
|
||||
}
|
||||
|
||||
const hasAutomatedHandledReports = async (db, id) => {
|
||||
return (await db.get(SQL`SELECT COUNT(*) AS c FROM reports WHERE userId = ${id} AND isAutomatic = 1 AND isHandled = 1`)).c > 0;
|
||||
}
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/profile/get/:username', handleErrorAsync(async (req, res) => {
|
||||
|
@ -121,6 +161,14 @@ router.post('/profile/save', handleErrorAsync(async (req, res) => {
|
|||
)`);
|
||||
}
|
||||
|
||||
const sus = [...isSuspicious(req.body)];
|
||||
if (sus.length && !await hasAutomatedHandledReports(req.db, req.user.id)) {
|
||||
await req.db.get(SQL`
|
||||
INSERT INTO reports (id, userId, reporterId, isAutomatic, comment, isHandled)
|
||||
VALUES (${ulid()}, ${req.user.id}, null, 1, ${sus.join(', ')}, 0);
|
||||
`);
|
||||
}
|
||||
|
||||
if (req.body.teamName) {
|
||||
await caches.admins.invalidate();
|
||||
await caches.adminsFooter.invalidate();
|
||||
|
@ -135,4 +183,21 @@ router.post('/profile/delete/:locale', handleErrorAsync(async (req, res) => {
|
|||
return res.json(await fetchProfiles(req.db, req.user.username, true));
|
||||
}));
|
||||
|
||||
router.post('/profile/report/:username', handleErrorAsync(async (req, res) => {
|
||||
const user = await req.db.get(SQL`SELECT id FROM users WHERE usernameNorm = ${normalise(req.params.username)}`);
|
||||
if (!user) {
|
||||
return res.status(400).json({error: 'Missing user'});
|
||||
}
|
||||
if (!req.body.comment) {
|
||||
return res.status(400).json({error: 'Missing comment'});
|
||||
}
|
||||
|
||||
await req.db.get(SQL`
|
||||
INSERT INTO reports (id, userId, reporterId, isAutomatic, comment, isHandled)
|
||||
VALUES (${ulid()}, ${user.id}, ${req.user.id}, 0, ${req.body.comment}, 0);
|
||||
`);
|
||||
|
||||
return res.json('OK');
|
||||
}));
|
||||
|
||||
export default router;
|
||||
|
|
Reference in New Issue