wip
This commit is contained in:
parent
6c5d7e9e89
commit
3acb3f2234
|
@ -28,6 +28,7 @@
|
||||||
- Feat: 二要素認証でパスキーをサポートするようになりました
|
- Feat: 二要素認証でパスキーをサポートするようになりました
|
||||||
- Feat: 指定したユーザーが投稿したときに通知できるようになりました
|
- Feat: 指定したユーザーが投稿したときに通知できるようになりました
|
||||||
- Feat: プロフィールでのリンク検証
|
- Feat: プロフィールでのリンク検証
|
||||||
|
- Feat: モデレーションログ機能
|
||||||
- Feat: 通知をテストできるようになりました
|
- Feat: 通知をテストできるようになりました
|
||||||
- Feat: PWAのアイコンが設定できるようになりました
|
- Feat: PWAのアイコンが設定できるようになりました
|
||||||
- Enhance: サーバー名の略称が設定できるようになりました
|
- Enhance: サーバー名の略称が設定できるようになりました
|
||||||
|
|
|
@ -62,6 +62,8 @@ export const paramDef = {
|
||||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
sinceId: { type: 'string', format: 'misskey:id' },
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
type: { type: 'string', nullable: true },
|
||||||
|
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -78,6 +80,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
|
const query = this.queryService.makePaginationQuery(this.moderationLogsRepository.createQueryBuilder('report'), ps.sinceId, ps.untilId);
|
||||||
|
|
||||||
|
if (ps.type != null) {
|
||||||
|
query.andWhere('report.type = :type', { type: ps.type });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.userId != null) {
|
||||||
|
query.andWhere('report.userId = :userId', { userId: ps.userId });
|
||||||
|
}
|
||||||
|
|
||||||
const reports = await query.limit(ps.limit).getMany();
|
const reports = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
return await this.moderationLogEntityService.packMany(reports);
|
return await this.moderationLogEntityService.packMany(reports);
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentProfile = await this.userProfilesRepository.findOneBy({ userId: user.id });
|
const currentProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
|
||||||
await this.userProfilesRepository.update({ userId: user.id }, {
|
await this.userProfilesRepository.update({ userId: user.id }, {
|
||||||
moderationNote: ps.text,
|
moderationNote: ps.text,
|
||||||
|
@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
this.moderationLogService.log(me, 'userNoteUpdated', {
|
this.moderationLogService.log(me, 'userNoteUpdated', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
before: currentProfile?.moderationNote,
|
before: currentProfile.moderationNote,
|
||||||
after: ps.text,
|
after: ps.text,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||||
|
|
||||||
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
||||||
|
|
||||||
export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted'] as const;
|
export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted', 'clearQueue', 'promoteQueue'] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
updateMeta: {
|
updateMeta: {
|
||||||
|
@ -68,4 +68,6 @@ export type ModerationLogPayloads = {
|
||||||
roleId: string;
|
roleId: string;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
};
|
};
|
||||||
|
clearQueue: {};
|
||||||
|
promoteQueue: {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div>{{ i18n.ts.user }}: {{ log.userId }}</div>
|
<div>{{ i18n.ts.moderator }}: {{ log.userId }}</div>
|
||||||
|
|
||||||
|
<template v-if="log.type === 'suspend'">
|
||||||
|
<div>{{ i18n.ts.user }}: {{ log.info.targetId }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="log.type === 'unsuspend'">
|
||||||
|
<div>{{ i18n.ts.user }}: {{ log.info.targetId }}</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -11,35 +11,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="reports">
|
<div class="reports">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="inputs" style="display: flex;">
|
<div class="inputs" style="display: flex;">
|
||||||
<MkSelect v-model="state" style="margin: 0; flex: 1;">
|
<MkSelect v-model="type" style="margin: 0; flex: 1;">
|
||||||
<template #label>{{ i18n.ts.state }}</template>
|
<template #label>{{ i18n.ts.type }}</template>
|
||||||
<option value="all">{{ i18n.ts.all }}</option>
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
<option value="unresolved">{{ i18n.ts.unresolved }}</option>
|
<option value="unresolved">{{ i18n.ts.unresolved }}</option>
|
||||||
<option value="resolved">{{ i18n.ts.resolved }}</option>
|
<option value="resolved">{{ i18n.ts.resolved }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkSelect v-model="targetUserOrigin" style="margin: 0; flex: 1;">
|
<MkInput v-model="moderatorId" style="margin: 0; flex: 1;">
|
||||||
<template #label>{{ i18n.ts.reporteeOrigin }}</template>
|
<template #label>{{ i18n.ts.moderator }}(ID)</template>
|
||||||
<option value="combined">{{ i18n.ts.all }}</option>
|
</MkInput>
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
<MkSelect v-model="reporterOrigin" style="margin: 0; flex: 1;">
|
|
||||||
<template #label>{{ i18n.ts.reporterOrigin }}</template>
|
|
||||||
<option value="combined">{{ i18n.ts.all }}</option>
|
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
|
||||||
</MkSelect>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO
|
|
||||||
<div class="inputs" style="display: flex; padding-top: 1.2em;">
|
|
||||||
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" :spellcheck="false">
|
|
||||||
<span>{{ i18n.ts.username }}</span>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params().origin === 'local'">
|
|
||||||
<span>{{ i18n.ts.host }}</span>
|
|
||||||
</MkInput>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
|
@ -59,25 +40,22 @@ import { computed } from 'vue';
|
||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import XModLog from './modlog.ModLog.vue';
|
import XModLog from './modlog.ModLog.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
let logs = $shallowRef<InstanceType<typeof MkPagination>>();
|
let logs = $shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
let state = $ref('unresolved');
|
let type = $ref(null);
|
||||||
let reporterOrigin = $ref('combined');
|
let moderatorId = $ref('');
|
||||||
let targetUserOrigin = $ref('combined');
|
|
||||||
let searchUsername = $ref('');
|
|
||||||
let searchHost = $ref('');
|
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'admin/show-moderation-logs' as const,
|
endpoint: 'admin/show-moderation-logs' as const,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
state,
|
type,
|
||||||
reporterOrigin,
|
userId: moderatorId === '' ? null : moderatorId,
|
||||||
targetUserOrigin,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2521,11 +2521,42 @@ type MessagingMessage = {
|
||||||
type ModerationLog = {
|
type ModerationLog = {
|
||||||
id: ID;
|
id: ID;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
type: string;
|
|
||||||
info: Record<string, any>;
|
|
||||||
userId: User['id'];
|
userId: User['id'];
|
||||||
user: UserDetailed | null;
|
user: UserDetailed | null;
|
||||||
};
|
} & ({
|
||||||
|
type: 'updateMeta';
|
||||||
|
info: ModerationLogPayloads['updateMeta'];
|
||||||
|
} | {
|
||||||
|
type: 'suspend';
|
||||||
|
info: ModerationLogPayloads['suspend'];
|
||||||
|
} | {
|
||||||
|
type: 'unsuspend';
|
||||||
|
info: ModerationLogPayloads['unsuspend'];
|
||||||
|
} | {
|
||||||
|
type: 'userNoteUpdated';
|
||||||
|
info: ModerationLogPayloads['userNoteUpdated'];
|
||||||
|
} | {
|
||||||
|
type: 'addEmoji';
|
||||||
|
info: ModerationLogPayloads['addEmoji'];
|
||||||
|
} | {
|
||||||
|
type: 'roleAssigned';
|
||||||
|
info: ModerationLogPayloads['roleAssigned'];
|
||||||
|
} | {
|
||||||
|
type: 'roleUnassigned';
|
||||||
|
info: ModerationLogPayloads['roleUnassigned'];
|
||||||
|
} | {
|
||||||
|
type: 'roleUpdated';
|
||||||
|
info: ModerationLogPayloads['roleUpdated'];
|
||||||
|
} | {
|
||||||
|
type: 'roleDeleted';
|
||||||
|
info: ModerationLogPayloads['roleDeleted'];
|
||||||
|
} | {
|
||||||
|
type: 'clearQueue';
|
||||||
|
info: ModerationLogPayloads['clearQueue'];
|
||||||
|
} | {
|
||||||
|
type: 'promoteQueue';
|
||||||
|
info: ModerationLogPayloads['promoteQueue'];
|
||||||
|
});
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||||
|
@ -2872,6 +2903,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||||
|
// src/entities.ts:579:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
|
@ -44,3 +44,48 @@ export const permissions = [
|
||||||
'read:flash-likes',
|
'read:flash-likes',
|
||||||
'write:flash-likes',
|
'write:flash-likes',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const moderationLogTypes = ['updateMeta', 'suspend', 'unsuspend', 'userNoteUpdated', 'addEmoji', 'roleAssigned', 'roleUnassigned', 'roleUpdated', 'roleDeleted', 'clearQueue', 'promoteQueue'] as const;
|
||||||
|
|
||||||
|
export type ModerationLogPayloads = {
|
||||||
|
updateMeta: {
|
||||||
|
before: any | null;
|
||||||
|
after: any | null;
|
||||||
|
};
|
||||||
|
suspend: {
|
||||||
|
targetId: string;
|
||||||
|
};
|
||||||
|
unsuspend: {
|
||||||
|
targetId: string;
|
||||||
|
};
|
||||||
|
userNoteUpdated: {
|
||||||
|
userId: string;
|
||||||
|
before: string | null;
|
||||||
|
after: string | null;
|
||||||
|
};
|
||||||
|
addEmoji: {
|
||||||
|
emojiId: string;
|
||||||
|
};
|
||||||
|
roleAssigned: {
|
||||||
|
userId: string;
|
||||||
|
roleId: string;
|
||||||
|
roleName: string;
|
||||||
|
expiresAt: string | null;
|
||||||
|
};
|
||||||
|
roleUnassigned: {
|
||||||
|
userId: string;
|
||||||
|
roleId: string;
|
||||||
|
roleName: string;
|
||||||
|
};
|
||||||
|
roleUpdated: {
|
||||||
|
roleId: string;
|
||||||
|
before: any;
|
||||||
|
after: any;
|
||||||
|
};
|
||||||
|
roleDeleted: {
|
||||||
|
roleId: string;
|
||||||
|
roleName: string;
|
||||||
|
};
|
||||||
|
clearQueue: {};
|
||||||
|
promoteQueue: {};
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ModerationLogPayloads, moderationLogTypes } from './consts.js';
|
||||||
|
|
||||||
export type ID = string;
|
export type ID = string;
|
||||||
export type DateString = string;
|
export type DateString = string;
|
||||||
|
|
||||||
|
@ -570,8 +572,39 @@ export type OriginType = 'combined' | 'local' | 'remote';
|
||||||
export type ModerationLog = {
|
export type ModerationLog = {
|
||||||
id: ID;
|
id: ID;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
type: string;
|
|
||||||
info: Record<string, any>;
|
|
||||||
userId: User['id'];
|
userId: User['id'];
|
||||||
user: UserDetailed | null;
|
user: UserDetailed | null;
|
||||||
};
|
} & ({
|
||||||
|
type: 'updateMeta';
|
||||||
|
info: ModerationLogPayloads['updateMeta'];
|
||||||
|
} | {
|
||||||
|
type: 'suspend';
|
||||||
|
info: ModerationLogPayloads['suspend'];
|
||||||
|
} | {
|
||||||
|
type: 'unsuspend';
|
||||||
|
info: ModerationLogPayloads['unsuspend'];
|
||||||
|
} | {
|
||||||
|
type: 'userNoteUpdated';
|
||||||
|
info: ModerationLogPayloads['userNoteUpdated'];
|
||||||
|
} | {
|
||||||
|
type: 'addEmoji';
|
||||||
|
info: ModerationLogPayloads['addEmoji'];
|
||||||
|
} | {
|
||||||
|
type: 'roleAssigned';
|
||||||
|
info: ModerationLogPayloads['roleAssigned'];
|
||||||
|
} | {
|
||||||
|
type: 'roleUnassigned';
|
||||||
|
info: ModerationLogPayloads['roleUnassigned'];
|
||||||
|
} | {
|
||||||
|
type: 'roleUpdated';
|
||||||
|
info: ModerationLogPayloads['roleUpdated'];
|
||||||
|
} | {
|
||||||
|
type: 'roleDeleted';
|
||||||
|
info: ModerationLogPayloads['roleDeleted'];
|
||||||
|
} | {
|
||||||
|
type: 'clearQueue';
|
||||||
|
info: ModerationLogPayloads['clearQueue'];
|
||||||
|
} | {
|
||||||
|
type: 'promoteQueue';
|
||||||
|
info: ModerationLogPayloads['promoteQueue'];
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue