summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2020-10-19 19:29:04 +0900
committersyuilo <syuilotan@yahoo.co.jp>2020-10-19 19:29:04 +0900
commit87f61e714ad3b17856a6a5ac66051707badb3bd0 (patch)
tree353b384f026bb326d16b7194d5fa5dfee7f12069 /src
parent12.48.3 (diff)
downloadmisskey-87f61e714ad3b17856a6a5ac66051707badb3bd0.tar.gz
misskey-87f61e714ad3b17856a6a5ac66051707badb3bd0.tar.bz2
misskey-87f61e714ad3b17856a6a5ac66051707badb3bd0.zip
Resolve #6087
Diffstat (limited to 'src')
-rw-r--r--src/client/components/abuse-report-window.vue85
-rw-r--r--src/client/components/note.vue17
-rw-r--r--src/client/components/page-window.vue8
-rw-r--r--src/client/components/sidebar.vue7
-rw-r--r--src/client/components/ui/window.vue9
-rw-r--r--src/client/os.ts6
-rw-r--r--src/client/pages/instance/abuses.vue163
-rw-r--r--src/client/router.ts1
-rw-r--r--src/client/scripts/get-user-menu.ts14
-rw-r--r--src/models/entities/abuse-user-report.ts41
-rw-r--r--src/models/repositories/abuse-user-report.ts9
-rw-r--r--src/remote/activitypub/kernel/flag/index.ts4
-rw-r--r--src/server/api/endpoints/admin/abuse-user-reports.ts38
-rw-r--r--src/server/api/endpoints/admin/resolve-abuse-user-report.ts (renamed from src/server/api/endpoints/admin/remove-abuse-user-report.ts)7
-rw-r--r--src/server/api/endpoints/users/report-abuse.ts10
15 files changed, 396 insertions, 23 deletions
diff --git a/src/client/components/abuse-report-window.vue b/src/client/components/abuse-report-window.vue
new file mode 100644
index 0000000000..1d87cb1802
--- /dev/null
+++ b/src/client/components/abuse-report-window.vue
@@ -0,0 +1,85 @@
+<template>
+<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')">
+ <template #header>
+ <Fa :icon="faExclamationCircle" style="margin-right: 0.5em;"/>
+ <i18n-t keypath="reportAbuseOf" tag="span">
+ <template #name>
+ <b><MkAcct :user="user"/></b>
+ </template>
+ </i18n-t>
+ </template>
+ <div class="dpvffvvy">
+ <div class="_section">
+ <div class="_content">
+ <MkTextarea v-model:value="comment">
+ <span>{{ $t('details') }}</span>
+ <template #desc>{{ $t('fillAbuseReportDescription') }}</template>
+ </MkTextarea>
+ </div>
+ </div>
+ <div class="_section">
+ <div class="_content">
+ <MkButton @click="send" primary full :disabled="comment.length === 0">{{ $t('send') }}</MkButton>
+ </div>
+ </div>
+ </div>
+</XWindow>
+</template>
+
+<script lang="ts">
+import { defineComponent, markRaw } from 'vue';
+import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
+import XWindow from '@/components/ui/window.vue';
+import MkTextarea from '@/components/ui/textarea.vue';
+import MkButton from '@/components/ui/button.vue';
+import * as os from '@/os';
+
+export default defineComponent({
+ components: {
+ XWindow,
+ MkTextarea,
+ MkButton,
+ },
+
+ props: {
+ user: {
+ type: Object,
+ required: true,
+ },
+ initialComment: {
+ type: String,
+ required: false,
+ },
+ },
+
+ emits: ['closed'],
+
+ data() {
+ return {
+ comment: this.initialComment || '',
+ faExclamationCircle,
+ };
+ },
+
+ methods: {
+ send() {
+ os.apiWithDialog('users/report-abuse', {
+ userId: this.user.id,
+ comment: this.comment,
+ }, undefined, res => {
+ os.dialog({
+ type: 'success',
+ text: this.$t('abuseReported')
+ });
+ this.$refs.window.close();
+ });
+ }
+ },
+});
+</script>
+
+<style lang="scss" scoped>
+.dpvffvvy {
+ --section-padding: 16px;
+}
+</style>
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index cb364a04c9..85bdb9c6fb 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -101,7 +101,7 @@
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue';
-import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons';
+import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
@@ -637,6 +637,21 @@ export default defineComponent({
}]
: []
),
+ ...(this.appearNote.userId != this.$store.state.i.id ? [
+ null,
+ {
+ icon: faExclamationCircle,
+ text: this.$t('reportAbuse'),
+ action: () => {
+ const u = `${url}/notes/${this.appearNote.id}`;
+ os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
+ user: this.appearNote.user,
+ initialComment: `Note: ${u}\n-----\n`
+ }, {}, 'closed');
+ }
+ }]
+ : []
+ ),
...(this.appearNote.userId == this.$store.state.i.id || this.$store.state.i.isModerator || this.$store.state.i.isAdmin ? [
null,
this.appearNote.userId == this.$store.state.i.id ? {
diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue
index 4a605bde69..2673b3f8ec 100644
--- a/src/client/components/page-window.vue
+++ b/src/client/components/page-window.vue
@@ -7,7 +7,7 @@
<button class="_button" @click="expand" v-tooltip="$t('showInPage')"><Fa :icon="faExpandAlt"/></button>
<button class="_button" @click="popout" v-tooltip="$t('popout')"><Fa :icon="faExternalLinkAlt"/></button>
</template>
- <div style="min-height: 100%; background: var(--bg);">
+ <div class="yrolvcoq" style="min-height: 100%; background: var(--bg);">
<component :is="component" v-bind="props" :ref="changePage"/>
</div>
</XWindow>
@@ -84,3 +84,9 @@ export default defineComponent({
},
});
</script>
+
+<style lang="scss" scoped>
+.yrolvcoq {
+ --section-padding: 16px;
+}
+</style>
diff --git a/src/client/components/sidebar.vue b/src/client/components/sidebar.vue
index 7548b136ea..383378241b 100644
--- a/src/client/components/sidebar.vue
+++ b/src/client/components/sidebar.vue
@@ -46,7 +46,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
-import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream } from '@fortawesome/free-solid-svg-icons';
+import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
import { host, instanceName } from '@/config';
import { search } from '@/scripts/search';
@@ -219,6 +219,11 @@ export default defineComponent({
icon: faBroadcastTower,
}, {
type: 'link',
+ text: this.$t('abuseReports'),
+ to: '/instance/abuses',
+ icon: faExclamationCircle,
+ }, {
+ type: 'link',
text: this.$t('logs'),
to: '/instance/logs',
icon: faStream,
diff --git a/src/client/components/ui/window.vue b/src/client/components/ui/window.vue
index 4a51a4d8e3..781c6ef151 100644
--- a/src/client/components/ui/window.vue
+++ b/src/client/components/ui/window.vue
@@ -7,7 +7,9 @@
<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown">
<slot name="header"></slot>
</span>
- <slot name="buttons"></slot>
+ <slot name="buttons">
+ <button class="_button" style="pointer-events: none;"></button>
+ </slot>
</div>
<div class="body" v-if="padding">
<div class="_section">
@@ -371,8 +373,6 @@ export default defineComponent({
width: 100%;
height: 100%;
- --section-padding: 16px;
-
> .header {
$height: 50px;
display: flex;
@@ -380,7 +380,6 @@ export default defineComponent({
z-index: 1;
flex-shrink: 0;
box-shadow: 0px 1px var(--divider);
- cursor: move;
user-select: none;
height: $height;
@@ -400,6 +399,8 @@ export default defineComponent({
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ text-align: center;
+ cursor: move;
}
}
diff --git a/src/client/os.ts b/src/client/os.ts
index 6adfc28956..3241f82e5d 100644
--- a/src/client/os.ts
+++ b/src/client/os.ts
@@ -4,6 +4,7 @@ import Stream from '@/scripts/stream';
import { store } from '@/store';
import { apiUrl } from '@/config';
import MkPostFormDialog from '@/components/post-form-dialog.vue';
+import MkWaitingDialog from '@/components/waiting-dialog.vue';
const ua = navigator.userAgent.toLowerCase();
export const isMobile = /mobile|iphone|ipad|android/.test(ua);
@@ -73,7 +74,7 @@ export function apiWithDialog(
promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => {
dialog({
type: 'error',
- text: e.message + '\n' + (e as any).id,
+ text: e.message + '<br>' + (e as any).id,
});
});
@@ -111,7 +112,8 @@ export function promiseDialog<T extends Promise<any>>(
}
});
- popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
+ // NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
+ popup(MkWaitingDialog, {
success: success,
showing: showing,
text: text,
diff --git a/src/client/pages/instance/abuses.vue b/src/client/pages/instance/abuses.vue
new file mode 100644
index 0000000000..90a2656e10
--- /dev/null
+++ b/src/client/pages/instance/abuses.vue
@@ -0,0 +1,163 @@
+<template>
+<div class="">
+ <div class="_section reports">
+ <div class="_content">
+ <div class="inputs" style="display: flex;">
+ <MkSelect v-model:value="state" style="margin: 0; flex: 1;">
+ <template #label>{{ $t('state') }}</template>
+ <option value="all">{{ $t('all') }}</option>
+ <option value="unresolved">{{ $t('unresolved') }}</option>
+ <option value="resolved">{{ $t('resolved') }}</option>
+ </MkSelect>
+ <MkSelect v-model:value="targetUserOrigin" style="margin: 0; flex: 1;">
+ <template #label>{{ $t('targetUserOrigin') }}</template>
+ <option value="combined">{{ $t('all') }}</option>
+ <option value="local">{{ $t('local') }}</option>
+ <option value="remote">{{ $t('remote') }}</option>
+ </MkSelect>
+ <MkSelect v-model:value="reporterOrigin" style="margin: 0; flex: 1;">
+ <template #label>{{ $t('reporterOrigin') }}</template>
+ <option value="combined">{{ $t('all') }}</option>
+ <option value="local">{{ $t('local') }}</option>
+ <option value="remote">{{ $t('remote') }}</option>
+ </MkSelect>
+ </div>
+ <!-- TODO
+ <div class="inputs" style="display: flex; padding-top: 1.2em;">
+ <MkInput v-model:value="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()">
+ <span>{{ $t('username') }}</span>
+ </MkInput>
+ <MkInput v-model:value="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:value="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'">
+ <span>{{ $t('host') }}</span>
+ </MkInput>
+ </div>
+ -->
+
+ <MkPagination :pagination="pagination" #default="{items}" ref="reports" :auto-margin="false" style="margin-top: var(--margin);">
+ <div class="bcekxzvu _card _vMargin" v-for="report in items" :key="report.id">
+ <div class="_content target">
+ <MkAvatar class="avatar" :user="report.targetUser"/>
+ <div class="info">
+ <MkUserName class="name" :user="report.targetUser"/>
+ <div class="acct">@{{ acct(report.targetUser) }}</div>
+ </div>
+ </div>
+ <div class="_content">
+ <div>
+ <Mfm :text="report.comment"/>
+ </div>
+ <hr>
+ <div>Reporter: <MkAcct :user="report.reporter"/></div>
+ <div><MkTime :time="report.createdAt"/></div>
+ </div>
+ <div class="_footer">
+ <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div>
+ <MkButton @click="resolve(report)" primary v-if="!report.resolved">{{ $t('abuseMarkAsResolved') }}</MkButton>
+ </div>
+ </div>
+ </MkPagination>
+ </div>
+ </div>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { faPlus, faUsers, faSearch, faBookmark, faMicrophoneSlash, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
+import { faSnowflake, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
+import parseAcct from '../../../misc/acct/parse';
+import MkButton from '@/components/ui/button.vue';
+import MkInput from '@/components/ui/input.vue';
+import MkSelect from '@/components/ui/select.vue';
+import MkPagination from '@/components/ui/pagination.vue';
+import { acct } from '../../filters/user';
+import * as os from '@/os';
+
+export default defineComponent({
+ components: {
+ MkButton,
+ MkInput,
+ MkSelect,
+ MkPagination,
+ },
+
+ data() {
+ return {
+ INFO: {
+ header: [{
+ title: this.$t('abuseReports'),
+ icon: faExclamationCircle
+ }],
+ },
+ searchUsername: '',
+ searchHost: '',
+ state: 'unresolved',
+ reporterOrigin: 'combined',
+ targetUserOrigin: 'combined',
+ pagination: {
+ endpoint: 'admin/abuse-user-reports',
+ limit: 10,
+ params: () => ({
+ state: this.state,
+ reporterOrigin: this.reporterOrigin,
+ targetUserOrigin: this.targetUserOrigin,
+ }),
+ },
+ faPlus, faUsers, faSearch, faBookmark, farBookmark, faMicrophoneSlash, faSnowflake
+ }
+ },
+
+ watch: {
+ state() {
+ this.$refs.reports.reload();
+ },
+
+ reporterOrigin() {
+ this.$refs.reports.reload();
+ },
+
+ targetUserOrigin() {
+ this.$refs.reports.reload();
+ },
+ },
+
+ methods: {
+ acct,
+
+ resolve(report) {
+ os.apiWithDialog('admin/resolve-abuse-user-report', {
+ reportId: report.id,
+ }).then(() => {
+ this.$refs.reports.removeItem(item => item.id === report.id);
+ });
+ },
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.bcekxzvu {
+ > .target {
+ display: flex;
+ width: 100%;
+ box-sizing: border-box;
+ text-align: left;
+ align-items: center;
+
+ > .avatar {
+ width: 42px;
+ height: 42px;
+ }
+
+ > .info {
+ margin-left: 0.3em;
+ padding: 0 8px;
+ flex: 1;
+
+ > .name {
+ font-weight: bold;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/client/router.ts b/src/client/router.ts
index fc67f6ecfd..c9c7a32835 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -86,6 +86,7 @@ export const router = createRouter({
{ path: '/instance/federation', component: page('instance/federation') },
{ path: '/instance/relays', component: page('instance/relays') },
{ path: '/instance/announcements', component: page('instance/announcements') },
+ { path: '/instance/abuses', component: page('instance/abuses') },
{ path: '/notes/:note', name: 'note', component: page('note') },
{ path: '/tags/:tag', component: page('tag') },
{ path: '/auth/:token', component: page('auth') },
diff --git a/src/client/scripts/get-user-menu.ts b/src/client/scripts/get-user-menu.ts
index 63c3ae43b6..cace2e1425 100644
--- a/src/client/scripts/get-user-menu.ts
+++ b/src/client/scripts/get-user-menu.ts
@@ -1,4 +1,4 @@
-import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash, faPlug } from '@fortawesome/free-solid-svg-icons';
+import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { i18n } from '@/i18n';
import copyToClipboard from '@/scripts/copy-to-clipboard';
@@ -102,6 +102,12 @@ export function getUserMenu(user) {
});
}
+ async function reportAbuse() {
+ os.popup(await import('@/components/abuse-report-window.vue'), {
+ user: user,
+ }, {}, 'closed');
+ }
+
async function getConfirmed(text: string): Promise<boolean> {
const confirm = await os.dialog({
type: 'warning',
@@ -157,6 +163,12 @@ export function getUserMenu(user) {
action: toggleBlock
}]);
+ menu = menu.concat([null, {
+ icon: faExclamationCircle,
+ text: i18n.global.t('reportAbuse'),
+ action: reportAbuse
+ }]);
+
if (store.getters.isSignedIn && (store.state.i.isAdmin || store.state.i.isModerator)) {
menu = menu.concat([null, {
icon: faMicrophoneSlash,
diff --git a/src/models/entities/abuse-user-report.ts b/src/models/entities/abuse-user-report.ts
index 43ab56023a..c0cff139f6 100644
--- a/src/models/entities/abuse-user-report.ts
+++ b/src/models/entities/abuse-user-report.ts
@@ -3,7 +3,6 @@ import { User } from './user';
import { id } from '../id';
@Entity()
-@Index(['userId', 'reporterId'], { unique: true })
export class AbuseUserReport {
@PrimaryColumn(id())
public id: string;
@@ -16,13 +15,13 @@ export class AbuseUserReport {
@Index()
@Column(id())
- public userId: User['id'];
+ public targetUserId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE'
})
@JoinColumn()
- public user: User | null;
+ public targetUser: User | null;
@Index()
@Column(id())
@@ -34,8 +33,42 @@ export class AbuseUserReport {
@JoinColumn()
public reporter: User | null;
+ @Column({
+ ...id(),
+ nullable: true
+ })
+ public assigneeId: User['id'] | null;
+
+ @ManyToOne(type => User, {
+ onDelete: 'SET NULL'
+ })
+ @JoinColumn()
+ public assignee: User | null;
+
+ @Index()
+ @Column('boolean', {
+ default: false
+ })
+ public resolved: boolean;
+
@Column('varchar', {
- length: 512,
+ length: 2048,
})
public comment: string;
+
+ //#region Denormalized fields
+ @Index()
+ @Column('varchar', {
+ length: 128, nullable: true,
+ comment: '[Denormalized]'
+ })
+ public targetUserHost: string | null;
+
+ @Index()
+ @Column('varchar', {
+ length: 128, nullable: true,
+ comment: '[Denormalized]'
+ })
+ public reporterHost: string | null;
+ //#endregion
}
diff --git a/src/models/repositories/abuse-user-report.ts b/src/models/repositories/abuse-user-report.ts
index bff64c770c..dbdaa5ee15 100644
--- a/src/models/repositories/abuse-user-report.ts
+++ b/src/models/repositories/abuse-user-report.ts
@@ -15,14 +15,19 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
id: report.id,
createdAt: report.createdAt,
comment: report.comment,
+ resolved: report.resolved,
reporterId: report.reporterId,
- userId: report.userId,
+ targetUserId: report.targetUserId,
+ assigneeId: report.assigneeId,
reporter: Users.pack(report.reporter || report.reporterId, null, {
detail: true
}),
- user: Users.pack(report.user || report.userId, null, {
+ targetUser: Users.pack(report.targetUser || report.targetUserId, null, {
detail: true
}),
+ assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, {
+ detail: true
+ }) : null,
});
}
diff --git a/src/remote/activitypub/kernel/flag/index.ts b/src/remote/activitypub/kernel/flag/index.ts
index 9b3065b112..46ea789b4b 100644
--- a/src/remote/activitypub/kernel/flag/index.ts
+++ b/src/remote/activitypub/kernel/flag/index.ts
@@ -19,8 +19,10 @@ export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => {
await AbuseUserReports.insert({
id: genId(),
createdAt: new Date(),
- userId: users[0].id,
+ targetUserId: users[0].id,
+ targetUserHost: users[0].host,
reporterId: actor.id,
+ reporterHost: actor.host,
comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`
});
diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts
index d5a52184d1..6a7f380e16 100644
--- a/src/server/api/endpoints/admin/abuse-user-reports.ts
+++ b/src/server/api/endpoints/admin/abuse-user-reports.ts
@@ -23,12 +23,50 @@ export const meta = {
untilId: {
validator: $.optional.type(ID),
},
+
+ state: {
+ validator: $.optional.nullable.str,
+ default: null,
+ },
+
+ reporterOrigin: {
+ validator: $.optional.str.or([
+ 'combined',
+ 'local',
+ 'remote',
+ ]),
+ default: 'combined'
+ },
+
+ targetUserOrigin: {
+ validator: $.optional.str.or([
+ 'combined',
+ 'local',
+ 'remote',
+ ]),
+ default: 'combined'
+ },
}
};
export default define(meta, async (ps) => {
const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId);
+ switch (ps.state) {
+ case 'resolved': query.andWhere('report.resolved = TRUE'); break;
+ case 'unresolved': query.andWhere('report.resolved = FALSE'); break;
+ }
+
+ switch (ps.reporterOrigin) {
+ case 'local': query.andWhere('report.reporterHost IS NULL'); break;
+ case 'remote': query.andWhere('report.reporterHost IS NOT NULL'); break;
+ }
+
+ switch (ps.targetUserOrigin) {
+ case 'local': query.andWhere('report.targetUserHost IS NULL'); break;
+ case 'remote': query.andWhere('report.targetUserHost IS NOT NULL'); break;
+ }
+
const reports = await query.take(ps.limit!).getMany();
return await AbuseUserReports.packMany(reports);
diff --git a/src/server/api/endpoints/admin/remove-abuse-user-report.ts b/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index 150de5f5d4..0a62b5f365 100644
--- a/src/server/api/endpoints/admin/remove-abuse-user-report.ts
+++ b/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -16,12 +16,15 @@ export const meta = {
}
};
-export default define(meta, async (ps) => {
+export default define(meta, async (ps, me) => {
const report = await AbuseUserReports.findOne(ps.reportId);
if (report == null) {
throw new Error('report not found');
}
- await AbuseUserReports.delete(report.id);
+ await AbuseUserReports.update(report.id, {
+ resolved: true,
+ assigneeId: me.id,
+ });
});
diff --git a/src/server/api/endpoints/users/report-abuse.ts b/src/server/api/endpoints/users/report-abuse.ts
index a9b5543f3c..eaa4cd6258 100644
--- a/src/server/api/endpoints/users/report-abuse.ts
+++ b/src/server/api/endpoints/users/report-abuse.ts
@@ -26,7 +26,7 @@ export const meta = {
},
comment: {
- validator: $.str.range(1, 3000),
+ validator: $.str.range(1, 2048),
desc: {
'ja-JP': '迷惑行為の詳細'
}
@@ -72,9 +72,11 @@ export default define(meta, async (ps, me) => {
const report = await AbuseUserReports.save({
id: genId(),
createdAt: new Date(),
- userId: user.id,
+ targetUserId: user.id,
+ targetUserHost: user.host,
reporterId: me.id,
- comment: ps.comment
+ reporterHost: null,
+ comment: ps.comment,
});
// Publish event to moderators
@@ -90,7 +92,7 @@ export default define(meta, async (ps, me) => {
for (const moderator of moderators) {
publishAdminStream(moderator.id, 'newAbuseUserReport', {
id: report.id,
- userId: report.userId,
+ targetUserId: report.targetUserId,
reporterId: report.reporterId,
comment: report.comment
});