Merge remote-tracking branch 'misskey-dev/develop' into prismisskey

# Conflicts:
#	package.json
This commit is contained in:
mattyatea 2023-09-24 11:15:32 +09:00
commit 4e9aa25394
54 changed files with 582 additions and 241 deletions

View file

@ -189,9 +189,9 @@
"@types/jsrsasign": "10.5.9",
"@types/mime-types": "2.1.1",
"@types/ms": "0.7.31",
"@types/node": "20.6.3",
"@types/node": "20.6.4",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.10",
"@types/nodemailer": "6.4.11",
"@types/oauth": "0.9.2",
"@types/oauth2orize": "1.11.1",
"@types/oauth2orize-pkce": "0.1.0",

View file

@ -60,7 +60,7 @@ export class AnnouncementService {
}
@bindThis
public async create(values: Partial<MiAnnouncement>, moderator: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
const announcement = await this.announcementsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
@ -82,20 +82,24 @@ export class AnnouncementService {
announcement: packed,
});
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
userId: values.userId,
});
if (moderator) {
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
userId: values.userId,
});
}
} else {
this.globalEventService.publishBroadcastStream('announcementCreated', {
announcement: packed,
});
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
if (moderator) {
this.moderationLogService.log(moderator, 'createGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
}
}
return {
@ -104,6 +108,59 @@ export class AnnouncementService {
};
}
@bindThis
public async update(announcement: MiAnnouncement, values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<void> {
await this.announcementsRepository.update(announcement.id, {
updatedAt: new Date(),
title: values.title,
text: values.text,
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
imageUrl: values.imageUrl || null,
display: values.display,
icon: values.icon,
forExistingUsers: values.forExistingUsers,
needConfirmationToRead: values.needConfirmationToRead,
isActive: values.isActive,
});
const after = await this.announcementsRepository.findOneByOrFail({ id: announcement.id });
if (moderator) {
if (announcement.userId) {
this.moderationLogService.log(moderator, 'updateUserAnnouncement', {
announcementId: announcement.id,
before: announcement,
after: after,
});
} else {
this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', {
announcementId: announcement.id,
before: announcement,
after: after,
});
}
}
}
@bindThis
public async delete(announcement: MiAnnouncement, moderator?: MiUser): Promise<void> {
await this.announcementsRepository.delete(announcement.id);
if (moderator) {
if (announcement.userId) {
this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
} else {
this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
}
}
}
@bindThis
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
try {

View file

@ -12,12 +12,13 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiEmoji } from '@/models/Emoji.js';
import type { EmojisRepository, MiRole } from '@/models/_.js';
import type { EmojisRepository, MiRole, MiUser } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/server/api/stream/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@ -36,6 +37,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService,
) {
this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);
@ -66,7 +68,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive: boolean;
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
}): Promise<MiEmoji> {
}, moderator?: MiUser): Promise<MiEmoji> {
const emoji = await this.emojisRepository.insert({
id: this.idService.genId(),
updatedAt: new Date(),
@ -89,6 +91,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(emoji.id),
});
if (moderator) {
this.moderationLogService.log(moderator, 'addCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
}
return emoji;
@ -104,7 +113,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
isSensitive?: boolean;
localOnly?: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
}): Promise<void> {
}, moderator?: MiUser): Promise<void> {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
@ -140,6 +149,14 @@ export class CustomEmojiService implements OnApplicationShutdown {
emoji: updated,
});
}
if (moderator) {
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
emojiId: emoji.id,
before: emoji,
after: updated,
});
}
}
@bindThis
@ -231,7 +248,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
@bindThis
public async delete(id: MiEmoji['id']) {
public async delete(id: MiEmoji['id'], moderator?: MiUser) {
const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
await this.emojisRepository.delete(emoji.id);
@ -241,16 +258,30 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
if (moderator) {
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
}
@bindThis
public async deleteBulk(ids: MiEmoji['id'][]) {
public async deleteBulk(ids: MiEmoji['id'][], moderator?: MiUser) {
const emojis = await this.emojisRepository.findBy({
id: In(ids),
});
for (const emoji of emojis) {
await this.emojisRepository.delete(emoji.id);
if (moderator) {
this.moderationLogService.log(moderator, 'deleteCustomEmoji', {
emojiId: emoji.id,
emoji: emoji,
});
}
}
this.localEmojisCache.refresh();

View file

@ -493,6 +493,19 @@ export class RoleService implements OnApplicationShutdown {
}
}
@bindThis
public async delete(role: MiRole, moderator?: MiUser): Promise<void> {
await this.rolesRepository.delete({ id: role.id });
this.globalEventService.publishInternalEvent('roleDeleted', role);
if (moderator) {
this.moderationLogService.log(moderator, 'deleteRole', {
roleId: role.id,
role: role,
});
}
}
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -37,13 +38,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.delete(announcement.id);
await this.announcementService.delete(announcement, me);
});
}
}

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -45,13 +46,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.id });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementsRepository.update(announcement.id, {
await this.announcementService.update(announcement, {
updatedAt: new Date(),
title: ps.title,
text: ps.text,
@ -62,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
forExistingUsers: ps.forExistingUsers,
needConfirmationToRead: ps.needConfirmationToRead,
isActive: ps.isActive,
});
}, me);
});
}
}

View file

@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository, EmojisRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { ApiError } from '../../../error.js';
@ -67,7 +66,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
@ -97,11 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
localOnly: ps.localOnly ?? false,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
});
this.moderationLogService.log(me, 'addCustomEmoji', {
emojiId: emoji.id,
});
}, me);
return this.emojiEntityService.packDetailed(emoji);
});

View file

@ -30,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.deleteBulk(ps.ids);
await this.customEmojiService.deleteBulk(ps.ids, me);
});
}
}

View file

@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
await this.customEmojiService.delete(ps.id);
await this.customEmojiService.delete(ps.id, me);
});
}
}

View file

@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
});
}, me);
});
}
}

View file

@ -6,9 +6,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin', 'role'],
@ -41,17 +41,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps) => {
super(meta, paramDef, async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) {
throw new ApiError(meta.errors.noSuchRole);
}
await this.rolesRepository.delete({
id: ps.roleId,
});
this.globalEventService.publishInternalEvent('roleDeleted', role);
await this.roleService.delete(role, me);
});
}
}

View file

@ -33,6 +33,8 @@ export const moderationLogTypes = [
'unsuspend',
'updateUserNote',
'addCustomEmoji',
'updateCustomEmoji',
'deleteCustomEmoji',
'assignRole',
'unassignRole',
'updateRole',
@ -43,6 +45,10 @@ export const moderationLogTypes = [
'deleteNote',
'createGlobalAnnouncement',
'createUserAnnouncement',
'updateGlobalAnnouncement',
'updateUserAnnouncement',
'deleteGlobalAnnouncement',
'deleteUserAnnouncement',
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
@ -66,6 +72,16 @@ export type ModerationLogPayloads = {
};
addCustomEmoji: {
emojiId: string;
emoji: any;
};
updateCustomEmoji: {
emojiId: string;
before: any;
after: any;
};
deleteCustomEmoji: {
emojiId: string;
emoji: any;
};
assignRole: {
userId: string;
@ -85,7 +101,7 @@ export type ModerationLogPayloads = {
};
deleteRole: {
roleId: string;
roleName: string;
role: any;
};
clearQueue: Record<string, never>;
promoteQueue: Record<string, never>;
@ -107,6 +123,24 @@ export type ModerationLogPayloads = {
announcement: any;
userId: string;
};
updateGlobalAnnouncement: {
announcementId: string;
before: any;
after: any;
};
updateUserAnnouncement: {
announcementId: string;
before: any;
after: any;
};
deleteGlobalAnnouncement: {
announcementId: string;
announcement: any;
};
deleteUserAnnouncement: {
announcementId: string;
announcement: any;
};
resetPassword: {
targetId: string;
};

View file

@ -97,10 +97,10 @@
"@storybook/vue3-vite": "7.4.4",
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
"@types/estree": "1.0.1",
"@types/estree": "1.0.2",
"@types/matter-js": "0.19.0",
"@types/micromatch": "4.0.2",
"@types/node": "20.6.3",
"@types/node": "20.6.4",
"@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.0",
"@types/throttle-debounce": "5.0.0",

View file

@ -145,9 +145,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
<MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
<template #default="{ items }">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
</MkA>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
<MkUserCardMini :user="item.user" :withChart="false"/>
</MkA>
</div>
</template>
</MkPagination>
</div>

View file

@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tail">
<header :class="$style.header">
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: {{ notification.note.user.name ?? notification.note.user.username }}</span>
<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>

View file

@ -32,7 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
</div>
<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
<div class="_gaps_s">
<MkSwitch v-for="kind in (initialPermissions || Misskey.permissions)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
</div>
</div>
</MkSpacer>
</MkModalWindow>

View file

@ -2538,6 +2538,12 @@ type ModerationLog = {
} | {
type: 'addCustomEmoji';
info: ModerationLogPayloads['addCustomEmoji'];
} | {
type: 'updateCustomEmoji';
info: ModerationLogPayloads['updateCustomEmoji'];
} | {
type: 'deleteCustomEmoji';
info: ModerationLogPayloads['deleteCustomEmoji'];
} | {
type: 'assignRole';
info: ModerationLogPayloads['assignRole'];
@ -2556,6 +2562,30 @@ type ModerationLog = {
} | {
type: 'promoteQueue';
info: ModerationLogPayloads['promoteQueue'];
} | {
type: 'deleteDriveFile';
info: ModerationLogPayloads['deleteDriveFile'];
} | {
type: 'deleteNote';
info: ModerationLogPayloads['deleteNote'];
} | {
type: 'createGlobalAnnouncement';
info: ModerationLogPayloads['createGlobalAnnouncement'];
} | {
type: 'createUserAnnouncement';
info: ModerationLogPayloads['createUserAnnouncement'];
} | {
type: 'updateGlobalAnnouncement';
info: ModerationLogPayloads['updateGlobalAnnouncement'];
} | {
type: 'updateUserAnnouncement';
info: ModerationLogPayloads['updateUserAnnouncement'];
} | {
type: 'deleteGlobalAnnouncement';
info: ModerationLogPayloads['deleteGlobalAnnouncement'];
} | {
type: 'deleteUserAnnouncement';
info: ModerationLogPayloads['deleteUserAnnouncement'];
} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];
@ -2568,7 +2598,7 @@ type ModerationLog = {
});
// @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"];
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance"];
// @public (undocumented)
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];

View file

@ -23,7 +23,7 @@
"@microsoft/api-extractor": "7.37.0",
"@swc/jest": "0.2.29",
"@types/jest": "29.5.5",
"@types/node": "20.6.3",
"@types/node": "20.6.4",
"@typescript-eslint/eslint-plugin": "6.7.2",
"@typescript-eslint/parser": "6.7.2",
"eslint": "8.50.0",

View file

@ -51,6 +51,8 @@ export const moderationLogTypes = [
'unsuspend',
'updateUserNote',
'addCustomEmoji',
'updateCustomEmoji',
'deleteCustomEmoji',
'assignRole',
'unassignRole',
'updateRole',
@ -61,6 +63,10 @@ export const moderationLogTypes = [
'deleteNote',
'createGlobalAnnouncement',
'createUserAnnouncement',
'updateGlobalAnnouncement',
'updateUserAnnouncement',
'deleteGlobalAnnouncement',
'deleteUserAnnouncement',
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
@ -84,6 +90,16 @@ export type ModerationLogPayloads = {
};
addCustomEmoji: {
emojiId: string;
emoji: any;
};
updateCustomEmoji: {
emojiId: string;
before: any;
after: any;
};
deleteCustomEmoji: {
emojiId: string;
emoji: any;
};
assignRole: {
userId: string;
@ -103,7 +119,7 @@ export type ModerationLogPayloads = {
};
deleteRole: {
roleId: string;
roleName: string;
role: any;
};
clearQueue: Record<string, never>;
promoteQueue: Record<string, never>;
@ -125,6 +141,24 @@ export type ModerationLogPayloads = {
announcement: any;
userId: string;
};
updateGlobalAnnouncement: {
announcementId: string;
before: any;
after: any;
};
updateUserAnnouncement: {
announcementId: string;
before: any;
after: any;
};
deleteGlobalAnnouncement: {
announcementId: string;
announcement: any;
};
deleteUserAnnouncement: {
announcementId: string;
announcement: any;
};
resetPassword: {
targetId: string;
};

View file

@ -589,6 +589,12 @@ export type ModerationLog = {
} | {
type: 'addCustomEmoji';
info: ModerationLogPayloads['addCustomEmoji'];
} | {
type: 'updateCustomEmoji';
info: ModerationLogPayloads['updateCustomEmoji'];
} | {
type: 'deleteCustomEmoji';
info: ModerationLogPayloads['deleteCustomEmoji'];
} | {
type: 'assignRole';
info: ModerationLogPayloads['assignRole'];
@ -607,6 +613,30 @@ export type ModerationLog = {
} | {
type: 'promoteQueue';
info: ModerationLogPayloads['promoteQueue'];
} | {
type: 'deleteDriveFile';
info: ModerationLogPayloads['deleteDriveFile'];
} | {
type: 'deleteNote';
info: ModerationLogPayloads['deleteNote'];
} | {
type: 'createGlobalAnnouncement';
info: ModerationLogPayloads['createGlobalAnnouncement'];
} | {
type: 'createUserAnnouncement';
info: ModerationLogPayloads['createUserAnnouncement'];
} | {
type: 'updateGlobalAnnouncement';
info: ModerationLogPayloads['updateGlobalAnnouncement'];
} | {
type: 'updateUserAnnouncement';
info: ModerationLogPayloads['updateUserAnnouncement'];
} | {
type: 'deleteGlobalAnnouncement';
info: ModerationLogPayloads['deleteGlobalAnnouncement'];
} | {
type: 'deleteUserAnnouncement';
info: ModerationLogPayloads['deleteUserAnnouncement'];
} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];