Skip to content

Commit

Permalink
enhance: サーバーごとにモデレーションノートを残せるように
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Feb 22, 2024
1 parent fb0eb5a commit 26c8b53
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
## 202x.x.x (unreleased)

### General
- Enhance: サーバーごとにモデレーションノートを残せるように

### Client
- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
Expand Down
6 changes: 5 additions & 1 deletion locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9172,7 +9172,7 @@ export interface Locale extends ILocale {
*/
"updateServerSettings": string;
/**
* モデレーションノート更新
* ユーザーのモデレーションノート更新
*/
"updateUserNote": string;
/**
Expand Down Expand Up @@ -9219,6 +9219,10 @@ export interface Locale extends ILocale {
* リモートサーバーを再開
*/
"unsuspendRemoteInstance": string;
/**
* リモートサーバーのモデレーションノート更新
*/
"updateRemoteInstanceNote": string;
/**
* ファイルをセンシティブ付与
*/
Expand Down
3 changes: 2 additions & 1 deletion locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,7 @@ _moderationLogTypes:
updateCustomEmoji: "カスタム絵文字更新"
deleteCustomEmoji: "カスタム絵文字削除"
updateServerSettings: "サーバー設定更新"
updateUserNote: "モデレーションノート更新"
updateUserNote: "ユーザーのモデレーションノート更新"
deleteDriveFile: "ファイルを削除"
deleteNote: "ノートを削除"
createGlobalAnnouncement: "全体のお知らせを作成"
Expand All @@ -2446,6 +2446,7 @@ _moderationLogTypes:
resetPassword: "パスワードをリセット"
suspendRemoteInstance: "リモートサーバーを停止"
unsuspendRemoteInstance: "リモートサーバーを再開"
updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新"
markSensitiveDriveFile: "ファイルをセンシティブ付与"
unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
resolveAbuseReport: "通報を解決"
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/migration/1708399372194-per-instance-mod-note.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class PerInstanceModNote1708399372194 {
name = 'PerInstanceModNote1708399372194'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" ADD "moderationNote" character varying(16384) NOT NULL DEFAULT ''`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "moderationNote"`);
}
}
9 changes: 8 additions & 1 deletion packages/backend/src/core/entities/InstanceEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import type { Packed } from '@/misc/json-schema.js';
import type { MiInstance } from '@/models/Instance.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
import { UtilityService } from '../UtilityService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { RoleService } from '@/core/RoleService.js';
import { MiUser } from '@/models/User.js';

@Injectable()
export class InstanceEntityService {
constructor(
private metaService: MetaService,
private roleService: RoleService,

private utilityService: UtilityService,
) {
Expand All @@ -22,8 +25,11 @@ export class InstanceEntityService {
@bindThis
public async pack(
instance: MiInstance,
me?: { id: MiUser['id']; } | null | undefined,
): Promise<Packed<'FederationInstance'>> {
const meta = await this.metaService.fetch();
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;

return {
id: instance.id,
firstRetrievedAt: instance.firstRetrievedAt.toISOString(),
Expand All @@ -48,6 +54,7 @@ export class InstanceEntityService {
themeColor: instance.themeColor,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
moderationNote: iAmModerator ? instance.moderationNote : null,
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,9 @@ export class MiInstance {
nullable: true,
})
public infoUpdatedAt: Date | null;

@Column('varchar', {
length: 16384, default: '',
})
public moderationNote: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,9 @@ export const packedFederationInstanceSchema = {
optional: false, nullable: true,
format: 'date-time',
},
moderationNote: {
type: 'string',
optional: true, nullable: true,
},
},
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const paramDef = {
properties: {
host: { type: 'string' },
isSuspended: { type: 'boolean' },
moderationNote: { type: 'string' },
},
required: ['host', 'isSuspended'],
required: ['host'],
} as const;

@Injectable()
Expand All @@ -47,9 +48,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

await this.federatedInstanceService.update(instance.id, {
isSuspended: ps.isSuspended,
moderationNote: ps.moderationNote,
});

if (instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended) {
this.moderationLogService.log(me, 'suspendRemoteInstance', {
id: instance.id,
Expand All @@ -62,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
});
}
}

if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
id: instance.id,
host: instance.host,
before: instance.moderationNote,
after: ps.moderationNote,
});
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const instance = await this.instancesRepository
.findOneBy({ host: this.utilityService.toPuny(ps.host) });

return instance ? await this.instanceEntityService.pack(instance) : null;
return instance ? await this.instanceEntityService.pack(instance, me) : null;
});
}
}
7 changes: 7 additions & 0 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const moderationLogTypes = [
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
'updateRemoteInstanceNote',
'markSensitiveDriveFile',
'unmarkSensitiveDriveFile',
'resolveAbuseReport',
Expand Down Expand Up @@ -209,6 +210,12 @@ export type ModerationLogPayloads = {
id: string;
host: string;
};
updateRemoteInstanceNote: {
id: string;
host: string;
before: string | null;
after: string | null;
};
markSensitiveDriveFile: {
fileId: string;
fileUserId: string | null;
Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/pages/admin/modlog.ModLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
</div>
</template>
<template v-else-if="log.type === 'updateRemoteInstanceNote'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div :class="$style.diff">
<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
</div>
</template>

<details>
<summary>raw</summary>
Expand Down
12 changes: 11 additions & 1 deletion packages/frontend/src/pages/instance-info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
<MkTextarea v-model="moderationNote" manualSave>
<template #label>{{ i18n.ts.moderationNote }}</template>
</MkTextarea>
</div>
</FormSection>

Expand Down Expand Up @@ -119,7 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue';
import { ref, computed, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkChart from '@/components/MkChart.vue';
import MkObjectView from '@/components/MkObjectView.vue';
Expand All @@ -141,6 +144,7 @@ import MkPagination from '@/components/MkPagination.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
import { dateString } from '@/filters/date.js';
import MkTextarea from '@/components/MkTextarea.vue';

const props = defineProps<{
host: string;
Expand All @@ -155,6 +159,7 @@ const suspended = ref(false);
const isBlocked = ref(false);
const isSilenced = ref(false);
const faviconUrl = ref<string | null>(null);
const moderationNote = ref('');

const usersPagination = {
endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
Expand All @@ -167,6 +172,10 @@ const usersPagination = {
offsetMode: true,
};

watch(moderationNote, async () => {
await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value });
});

async function fetch(): Promise<void> {
if (iAmAdmin) {
meta.value = await misskeyApi('admin/meta');
Expand All @@ -178,6 +187,7 @@ async function fetch(): Promise<void> {
isBlocked.value = instance.value?.isBlocked ?? false;
isSilenced.value = instance.value?.isSilenced ?? false;
faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
moderationNote.value = instance.value?.moderationNote;
}

async function toggleBlock(): Promise<void> {
Expand Down
5 changes: 4 additions & 1 deletion packages/misskey-js/etc/misskey-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,9 @@ type ModerationLog = {
} | {
type: 'unsuspendRemoteInstance';
info: ModerationLogPayloads['unsuspendRemoteInstance'];
} | {
type: 'updateRemoteInstanceNote';
info: ModerationLogPayloads['updateRemoteInstanceNote'];
} | {
type: 'markSensitiveDriveFile';
info: ModerationLogPayloads['markSensitiveDriveFile'];
Expand Down Expand Up @@ -2355,7 +2358,7 @@ type ModerationLog = {
});

// @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];

// @public (undocumented)
type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
Expand Down
4 changes: 3 additions & 1 deletion packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4480,6 +4480,7 @@ export type components = {
infoUpdatedAt: string | null;
/** Format: date-time */
latestRequestReceivedAt: string | null;
moderationNote?: string | null;
};
GalleryPost: {
/**
Expand Down Expand Up @@ -7213,7 +7214,8 @@ export type operations = {
content: {
'application/json': {
host: string;
isSuspended: boolean;
isSuspended?: boolean;
moderationNote?: string;
};
};
};
Expand Down
7 changes: 7 additions & 0 deletions packages/misskey-js/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const moderationLogTypes = [
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
'updateRemoteInstanceNote',
'markSensitiveDriveFile',
'unmarkSensitiveDriveFile',
'resolveAbuseReport',
Expand Down Expand Up @@ -261,6 +262,12 @@ export type ModerationLogPayloads = {
id: string;
host: string;
};
updateRemoteInstanceNote: {
id: string;
host: string;
before: string | null;
after: string | null;
};
markSensitiveDriveFile: {
fileId: string;
fileUserId: string | null;
Expand Down
3 changes: 3 additions & 0 deletions packages/misskey-js/src/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export type ModerationLog = {
} | {
type: 'unsuspendRemoteInstance';
info: ModerationLogPayloads['unsuspendRemoteInstance'];
} | {
type: 'updateRemoteInstanceNote';
info: ModerationLogPayloads['updateRemoteInstanceNote'];
} | {
type: 'markSensitiveDriveFile';
info: ModerationLogPayloads['markSensitiveDriveFile'];
Expand Down

0 comments on commit 26c8b53

Please sign in to comment.