Sharkey/packages/misskey-js/src/entities.ts

693 lines
14 KiB
TypeScript
Raw Normal View History

2023-09-29 02:29:54 +00:00
import { ModerationLogPayloads, notificationTypes } from './consts.js';
export type ID = string;
export type DateString = string;
type TODO = Record<string, any>;
// NOTE: 極力この型を使うのは避け、UserLite か UserDetailed か明示するように
export type User = UserLite | UserDetailed;
export type UserLite = {
id: ID;
username: string;
host: string | null;
name: string;
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
avatarUrl: string;
avatarBlurhash: string;
emojis: {
name: string;
url: string;
}[];
instance?: {
name: Instance['name'];
softwareName: Instance['softwareName'];
softwareVersion: Instance['softwareVersion'];
iconUrl: Instance['iconUrl'];
faviconUrl: Instance['faviconUrl'];
themeColor: Instance['themeColor'];
};
};
export type UserDetailed = UserLite & {
enhance: account migration (#10592) * copy block and mute then create follow and unfollow jobs * copy block and mute and update lists when detecting an account has moved * no need to care promise orders * refactor updating actor and target * automatically accept if a locked account had accepted an old account * fix exception format * prevent the old account from calling some endpoints * do not unfollow when moving * adjust following and follower counts * check movedToUri when receiving a follow request * skip if no need to adjust * Revert "disable account migration" This reverts commit 2321214c98591bcfe1385c1ab5bf0ff7b471ae1d. * fix translation specifier * fix checking alsoKnownAs and uri * fix updating account * fix refollowing locked account * decrease followersCount if followed by the old account * adjust following and followers counts when unfollowing * fix copying mutings * prohibit moved account from moving again * fix move service * allow app creation after moving * fix lint * remove unnecessary field * fix cache update * add e2e test * add e2e test of accepting the new account automatically * force follow if any error happens * remove unnecessary joins * use Array.map instead of for const of * ユーザーリストの移行は追加のみを行う * nanka iroiro * fix misskey-js? * :v: * 移行を行ったアカウントからのフォローリクエストの自動許可を調整 * newUriを外に出す * newUriを外に出す2 * clean up * fix newUri * prevent moving if the destination account has already moved * set alsoKnownAs via /i/update * fix database initialization * add return type * prohibit updating alsoKnownAs after moving * skip to add to alsoKnownAs if toUrl is known * skip adding to the list if it already has * use Acct.parse instead * rename error code * :art: * 制限を5から10に緩和 * movedTo(Uri), alsoKnownAsはユーザーidを返すように * test api res * fix * 元アカウントはミュートし続ける * :art: * unfollow * fix * getUserUriをUserEntityServiceに * ? * job! * :art: * instance => server * accountMovedShort, forbiddenBecauseYouAreMigrated * accountMovedShort * fix test * import, pin禁止 * 実績を凍結する * clean up * :v: * change message * ブロック, フォロー, ミュート, リストのインポートファイルの制限を32MiBに * Revert "ブロック, フォロー, ミュート, リストのインポートファイルの制限を32MiBに" This reverts commit 3bd7be35d8aa455cb01ae58f8172a71a50485db1. * validateAlsoKnownAs * 移行後2時間以内はインポート可能なファイルサイズを拡大 * clean up * どうせactorをupdatePersonで更新するならupdatePersonしか移行処理を発行しないことにする * handle error? * リモートからの移行処理の条件を是正 * log, port * fix * fix * enhance(dev): non-production環境でhttpサーバー間でもユーザー、ノートの連合が可能なように * refactor (use checkHttps) * MISSKEY_WEBFINGER_USE_HTTP * Environment Variable readme * NEVER USE IN PRODUCTION * fix punyHost * fix indent * fix * experimental --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-04-29 15:09:29 +00:00
alsoKnownAs: string[];
bannerBlurhash: string | null;
bannerColor: string | null;
bannerUrl: string | null;
birthday: string | null;
createdAt: DateString;
description: string | null;
ffVisibility: 'public' | 'followers' | 'private';
fields: {name: string; value: string}[];
verifiedLinks: string[];
followersCount: number;
followingCount: number;
hasPendingFollowRequestFromYou: boolean;
hasPendingFollowRequestToYou: boolean;
isAdmin: boolean;
isBlocked: boolean;
isBlocking: boolean;
isBot: boolean;
isCat: boolean;
isFollowed: boolean;
isFollowing: boolean;
isLocked: boolean;
isModerator: boolean;
isMuted: boolean;
isSilenced: boolean;
isSuspended: boolean;
lang: string | null;
lastFetchedAt?: DateString;
location: string | null;
enhance: account migration (#10592) * copy block and mute then create follow and unfollow jobs * copy block and mute and update lists when detecting an account has moved * no need to care promise orders * refactor updating actor and target * automatically accept if a locked account had accepted an old account * fix exception format * prevent the old account from calling some endpoints * do not unfollow when moving * adjust following and follower counts * check movedToUri when receiving a follow request * skip if no need to adjust * Revert "disable account migration" This reverts commit 2321214c98591bcfe1385c1ab5bf0ff7b471ae1d. * fix translation specifier * fix checking alsoKnownAs and uri * fix updating account * fix refollowing locked account * decrease followersCount if followed by the old account * adjust following and followers counts when unfollowing * fix copying mutings * prohibit moved account from moving again * fix move service * allow app creation after moving * fix lint * remove unnecessary field * fix cache update * add e2e test * add e2e test of accepting the new account automatically * force follow if any error happens * remove unnecessary joins * use Array.map instead of for const of * ユーザーリストの移行は追加のみを行う * nanka iroiro * fix misskey-js? * :v: * 移行を行ったアカウントからのフォローリクエストの自動許可を調整 * newUriを外に出す * newUriを外に出す2 * clean up * fix newUri * prevent moving if the destination account has already moved * set alsoKnownAs via /i/update * fix database initialization * add return type * prohibit updating alsoKnownAs after moving * skip to add to alsoKnownAs if toUrl is known * skip adding to the list if it already has * use Acct.parse instead * rename error code * :art: * 制限を5から10に緩和 * movedTo(Uri), alsoKnownAsはユーザーidを返すように * test api res * fix * 元アカウントはミュートし続ける * :art: * unfollow * fix * getUserUriをUserEntityServiceに * ? * job! * :art: * instance => server * accountMovedShort, forbiddenBecauseYouAreMigrated * accountMovedShort * fix test * import, pin禁止 * 実績を凍結する * clean up * :v: * change message * ブロック, フォロー, ミュート, リストのインポートファイルの制限を32MiBに * Revert "ブロック, フォロー, ミュート, リストのインポートファイルの制限を32MiBに" This reverts commit 3bd7be35d8aa455cb01ae58f8172a71a50485db1. * validateAlsoKnownAs * 移行後2時間以内はインポート可能なファイルサイズを拡大 * clean up * どうせactorをupdatePersonで更新するならupdatePersonしか移行処理を発行しないことにする * handle error? * リモートからの移行処理の条件を是正 * log, port * fix * fix * enhance(dev): non-production環境でhttpサーバー間でもユーザー、ノートの連合が可能なように * refactor (use checkHttps) * MISSKEY_WEBFINGER_USE_HTTP * Environment Variable readme * NEVER USE IN PRODUCTION * fix punyHost * fix indent * fix * experimental --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-04-29 15:09:29 +00:00
movedTo: string;
notesCount: number;
pinnedNoteIds: ID[];
pinnedNotes: Note[];
pinnedPage: Page | null;
pinnedPageId: string | null;
publicReactions: boolean;
securityKeys: boolean;
twoFactorEnabled: boolean;
updatedAt: DateString | null;
uri: string | null;
url: string | null;
notify: 'normal' | 'none';
};
export type UserGroup = TODO;
export type UserList = {
id: ID;
createdAt: DateString;
name: string;
userIds: User['id'][];
};
export type MeDetailed = UserDetailed & {
avatarId: DriveFile['id'];
bannerId: DriveFile['id'];
autoAcceptFollowed: boolean;
alwaysMarkNsfw: boolean;
carefulBot: boolean;
emailNotificationTypes: string[];
hasPendingReceivedFollowRequest: boolean;
hasUnreadAnnouncement: boolean;
hasUnreadAntenna: boolean;
hasUnreadMentions: boolean;
hasUnreadMessagingMessage: boolean;
hasUnreadNotification: boolean;
hasUnreadSpecifiedNotes: boolean;
hideOnlineStatus: boolean;
injectFeaturedNote: boolean;
integrations: Record<string, any>;
isDeleted: boolean;
isExplorable: boolean;
mutedWords: string[][];
2023-09-29 02:29:54 +00:00
notificationRecieveConfig: {
[notificationType in typeof notificationTypes[number]]?: {
type: 'all';
} | {
type: 'never';
} | {
type: 'following';
} | {
type: 'follower';
} | {
type: 'mutualFollow';
} | {
type: 'list';
userListId: string;
};
};
noCrawle: boolean;
receiveAnnouncementEmail: boolean;
usePasswordLessLogin: boolean;
unreadAnnouncements: Announcement[];
twoFactorBackupCodesStock: 'full' | 'partial' | 'none';
[other: string]: any;
};
export type MeDetailedWithSecret = MeDetailed & {
email: string;
emailVerified: boolean;
securityKeysList: {
id: string;
name: string;
lastUsed: string;
}[];
};
export type MeSignup = MeDetailedWithSecret & {
token: string;
};
export type DriveFile = {
id: ID;
createdAt: DateString;
isSensitive: boolean;
name: string;
thumbnailUrl: string;
url: string;
type: string;
size: number;
md5: string;
blurhash: string;
comment: string | null;
properties: Record<string, any>;
};
export type DriveFolder = TODO;
export type GalleryPost = {
id: ID;
createdAt: DateString;
updatedAt: DateString;
userId: User['id'];
user: User;
title: string;
description: string | null;
fileIds: DriveFile['id'][];
files: DriveFile[];
isSensitive: boolean;
likedCount: number;
isLiked?: boolean;
};
export type Note = {
id: ID;
createdAt: DateString;
text: string | null;
cw: string | null;
user: User;
userId: User['id'];
reply?: Note;
replyId: Note['id'];
renote?: Note;
renoteId: Note['id'];
files: DriveFile[];
fileIds: DriveFile['id'][];
visibility: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds?: User['id'][];
localOnly?: boolean;
myReaction?: string;
reactions: Record<string, number>;
renoteCount: number;
repliesCount: number;
clippedCount?: number;
poll?: {
expiresAt: DateString | null;
multiple: boolean;
choices: {
isVoted: boolean;
text: string;
votes: number;
}[];
};
emojis: {
name: string;
url: string;
}[];
uri?: string;
url?: string;
isHidden?: boolean;
};
export type NoteReaction = {
id: ID;
createdAt: DateString;
user: UserLite;
type: string;
};
export type Notification = {
id: ID;
createdAt: DateString;
isRead: boolean;
} & ({
type: 'reaction';
reaction: string;
user: User;
userId: User['id'];
note: Note;
} | {
type: 'reply';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'renote';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'quote';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'mention';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'note';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'pollEnded';
user: User;
userId: User['id'];
note: Note;
} | {
type: 'follow';
user: User;
userId: User['id'];
} | {
type: 'followRequestAccepted';
user: User;
userId: User['id'];
} | {
type: 'receiveFollowRequest';
user: User;
userId: User['id'];
} | {
type: 'groupInvited';
invitation: UserGroup;
user: User;
userId: User['id'];
2023-10-09 04:32:41 +00:00
} | {
type: 'achievementEarned';
achievement: string;
} | {
type: 'app';
header?: string | null;
body: string;
icon?: string | null;
} | {
type: 'test';
});
export type MessagingMessage = {
id: ID;
createdAt: DateString;
file: DriveFile | null;
fileId: DriveFile['id'] | null;
isRead: boolean;
reads: User['id'][];
text: string | null;
user: User;
userId: User['id'];
recipient?: User | null;
recipientId: User['id'] | null;
group?: UserGroup | null;
groupId: UserGroup['id'] | null;
};
export type CustomEmoji = {
id: string;
name: string;
url: string;
category: string;
aliases: string[];
};
export type LiteInstanceMetadata = {
maintainerName: string | null;
maintainerEmail: string | null;
version: string;
name: string | null;
shortName: string | null;
uri: string;
description: string | null;
langs: string[];
tosUrl: string | null;
repositoryUrl: string;
feedbackUrl: string;
impressumUrl: string | null;
privacyPolicyUrl: string | null;
disableRegistration: boolean;
disableLocalTimeline: boolean;
disableGlobalTimeline: boolean;
driveCapacityPerLocalUserMb: number;
driveCapacityPerRemoteUserMb: number;
emailRequiredForSignup: boolean;
enableHcaptcha: boolean;
hcaptchaSiteKey: string | null;
enableRecaptcha: boolean;
recaptchaSiteKey: string | null;
enableTurnstile: boolean;
turnstileSiteKey: string | null;
swPublickey: string | null;
themeColor: string | null;
mascotImageUrl: string | null;
bannerUrl: string | null;
serverErrorImageUrl: string | null;
infoImageUrl: string | null;
notFoundImageUrl: string | null;
iconUrl: string | null;
backgroundImageUrl: string | null;
logoImageUrl: string | null;
maxNoteTextLength: number;
enableEmail: boolean;
enableTwitterIntegration: boolean;
enableGithubIntegration: boolean;
enableDiscordIntegration: boolean;
enableServiceWorker: boolean;
emojis: CustomEmoji[];
defaultDarkTheme: string | null;
defaultLightTheme: string | null;
ads: {
id: ID;
ratio: number;
place: string;
url: string;
imageUrl: string;
}[];
notesPerOneAd: number;
translatorAvailable: boolean;
serverRules: string[];
};
export type DetailedInstanceMetadata = LiteInstanceMetadata & {
pinnedPages: string[];
pinnedClipId: string | null;
cacheRemoteFiles: boolean;
cacheRemoteSensitiveFiles: boolean;
requireSetup: boolean;
proxyAccountName: string | null;
features: Record<string, any>;
};
export type InstanceMetadata = LiteInstanceMetadata | DetailedInstanceMetadata;
export type AdminInstanceMetadata = DetailedInstanceMetadata & {
// TODO: There are more fields.
blockedHosts: string[];
app192IconUrl: string | null;
app512IconUrl: string | null;
manifestJsonOverride: string;
};
export type ServerInfo = {
machine: string;
cpu: {
model: string;
cores: number;
};
mem: {
total: number;
};
fs: {
total: number;
used: number;
};
};
export type Stats = {
notesCount: number;
originalNotesCount: number;
usersCount: number;
originalUsersCount: number;
instances: number;
driveUsageLocal: number;
driveUsageRemote: number;
};
export type Page = {
id: ID;
createdAt: DateString;
updatedAt: DateString;
userId: User['id'];
user: User;
content: Record<string, any>[];
variables: Record<string, any>[];
title: string;
name: string;
summary: string | null;
hideTitleWhenPinned: boolean;
alignCenter: boolean;
font: string;
script: string;
eyeCatchingImageId: DriveFile['id'] | null;
eyeCatchingImage: DriveFile | null;
attachedFiles: any;
likedCount: number;
isLiked?: boolean;
};
export type PageEvent = {
pageId: Page['id'];
event: string;
var: any;
userId: User['id'];
user: User;
};
export type Announcement = {
id: ID;
createdAt: DateString;
updatedAt: DateString | null;
text: string;
title: string;
imageUrl: string | null;
display: 'normal' | 'banner' | 'dialog';
icon: 'info' | 'warning' | 'error' | 'success';
needConfirmationToRead: boolean;
forYou: boolean;
isRead?: boolean;
};
export type Antenna = {
id: ID;
createdAt: DateString;
name: string;
keywords: string[][]; // TODO
excludeKeywords: string[][]; // TODO
src: 'home' | 'all' | 'users' | 'list' | 'group';
userListId: ID | null; // TODO
userGroupId: ID | null; // TODO
users: string[]; // TODO
caseSensitive: boolean;
localOnly: boolean;
notify: boolean;
withReplies: boolean;
withFile: boolean;
hasUnreadNote: boolean;
};
export type App = TODO;
export type AuthSession = {
id: ID;
app: App;
token: string;
};
export type Ad = TODO;
export type Clip = TODO;
export type NoteFavorite = {
id: ID;
createdAt: DateString;
noteId: Note['id'];
note: Note;
};
export type FollowRequest = {
id: ID;
follower: User;
followee: User;
};
export type Channel = {
id: ID;
// TODO
};
export type Following = {
id: ID;
createdAt: DateString;
followerId: User['id'];
followeeId: User['id'];
};
export type FollowingFolloweePopulated = Following & {
followee: UserDetailed;
};
export type FollowingFollowerPopulated = Following & {
follower: UserDetailed;
};
export type Blocking = {
id: ID;
createdAt: DateString;
blockeeId: User['id'];
blockee: UserDetailed;
};
export type Instance = {
id: ID;
firstRetrievedAt: DateString;
host: string;
usersCount: number;
notesCount: number;
followingCount: number;
followersCount: number;
driveUsage: number;
driveFiles: number;
latestRequestSentAt: DateString | null;
latestStatus: number | null;
latestRequestReceivedAt: DateString | null;
lastCommunicatedAt: DateString;
isNotResponding: boolean;
isSuspended: boolean;
isBlocked: boolean;
softwareName: string | null;
softwareVersion: string | null;
openRegistrations: boolean | null;
name: string | null;
description: string | null;
maintainerName: string | null;
maintainerEmail: string | null;
iconUrl: string | null;
faviconUrl: string | null;
themeColor: string | null;
infoUpdatedAt: DateString | null;
};
export type Signin = {
id: ID;
createdAt: DateString;
ip: string;
headers: Record<string, any>;
success: boolean;
};
enhance: 招待機能の改善 (#11195) * refactor(backend): 招待機能を改修 * feat(backend): 招待コードのcreate/delete/listエンドポイントを追加 * add(misskey-js): エンドポイントと型を追加 * change(backend): metaでinvite関連の情報も返すように * add(misskey-js): エンドポイントと型を追加 * add(backend): `/endpoints/invite/limit`を追加 * fix: createdByがnullableではなかったのを修正 * fix: relationが取得できていなかった問題を修正 * fix: パラメータを間違えていたのを修正 * feat(client): 招待ページを実装 * change(client): インスタンスメニューの「招待」押した場合に招待ページに飛ぶように変更 * feat: 招待コードをコピーできるように * change(backend): metaに招待コード発行に関する情報を持たせるのをやめる * feat: ロールごとに招待コードの発行上限数などを設定できるように * change(client): 招待コードをコピーしたときにダイアログを出すように * add: 招待に関する管理者用のエンドポイントを追加 * change(backend): モデレーターであれば作成者以外でも招待コードを削除できるように * change(backend): admin/invite/listはオフセットでページネーションするように * feat(client): 招待コードの管理ページを追加 * feat(client): 招待コードのリストをソートできるように * change: `admin/invite/create`のレスポンスを修正 * fix(client): 有効期限を指定できていなかった問題を修正 * refactor: 必要のない箇所を削除 * perf(backend): use limit() instead of take() * change(client): 作成ボタンを見た目を変更 * refactor: 招待コードの生成部分を共通化し、コード内に"01OI"のいずれかの文字を含まないように * fix(client): paginationの仕様が変わっていたので修正 * change(backend): expiresAtパラメータのnullを許容 * change(client): 有効期限を設けないときは日付の入力欄を非表示に * fix: 自身のポリシーよりもインスタンス側のポリシーが優先表示される問題を修正 * fix: n時間のときに「n時間間」となってしまうのを修正 * fix(backend): ポリシーが途中で変更されたときに作成可能数がマイナス表記になってしまうのを修正 * change(client): 招待コードのユーザー名が不明な理由を表示するように * update: CHANGELOG.md * lint * refactor * refactor * tweak ui * :art: * :art: * add(backend): indexを追加 * change(backend): indexの追加に伴う変更 * change(client): インスタンスメニューの「招待」の場所を変更 * add(frontend): MkInviteCode用のstorybookを追加 * Update misskey-js.api.md * fix(misskey-js): InviteのcreatedByの型が間違っていたのを修正 --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-07-15 00:57:58 +00:00
export type Invite = {
id: ID;
code: string;
expiresAt: DateString | null;
createdAt: DateString;
createdBy: UserLite | null;
usedBy: UserLite | null;
usedAt: DateString | null;
used: boolean;
}
export type InviteLimit = {
remaining: number;
}
export type UserSorting =
| '+follower'
| '-follower'
| '+createdAt'
| '-createdAt'
| '+updatedAt'
| '-updatedAt';
export type OriginType = 'combined' | 'local' | 'remote';
export type ModerationLog = {
id: ID;
createdAt: DateString;
userId: User['id'];
user: UserDetailed | null;
} & ({
type: 'updateServerSettings';
info: ModerationLogPayloads['updateServerSettings'];
} | {
type: 'suspend';
info: ModerationLogPayloads['suspend'];
} | {
type: 'unsuspend';
info: ModerationLogPayloads['unsuspend'];
} | {
type: 'updateUserNote';
info: ModerationLogPayloads['updateUserNote'];
} | {
type: 'addCustomEmoji';
info: ModerationLogPayloads['addCustomEmoji'];
2023-09-24 01:57:24 +00:00
} | {
type: 'updateCustomEmoji';
info: ModerationLogPayloads['updateCustomEmoji'];
} | {
type: 'deleteCustomEmoji';
info: ModerationLogPayloads['deleteCustomEmoji'];
} | {
type: 'assignRole';
info: ModerationLogPayloads['assignRole'];
} | {
type: 'unassignRole';
info: ModerationLogPayloads['unassignRole'];
2023-09-25 01:29:12 +00:00
} | {
type: 'createRole';
info: ModerationLogPayloads['createRole'];
} | {
type: 'updateRole';
info: ModerationLogPayloads['updateRole'];
} | {
type: 'deleteRole';
info: ModerationLogPayloads['deleteRole'];
} | {
type: 'clearQueue';
info: ModerationLogPayloads['clearQueue'];
} | {
type: 'promoteQueue';
info: ModerationLogPayloads['promoteQueue'];
2023-09-24 01:46:09 +00:00
} | {
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'];
2023-09-23 11:50:02 +00:00
} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];
} | {
type: 'suspendRemoteInstance';
info: ModerationLogPayloads['suspendRemoteInstance'];
} | {
type: 'unsuspendRemoteInstance';
info: ModerationLogPayloads['unsuspendRemoteInstance'];
2023-09-24 06:40:38 +00:00
} | {
type: 'markSensitiveDriveFile';
info: ModerationLogPayloads['markSensitiveDriveFile'];
} | {
type: 'unmarkSensitiveDriveFile';
info: ModerationLogPayloads['unmarkSensitiveDriveFile'];
2023-09-28 01:02:05 +00:00
} | {
type: 'createInvitation';
info: ModerationLogPayloads['createInvitation'];
2023-09-29 06:01:13 +00:00
} | {
type: 'createAd';
info: ModerationLogPayloads['createAd'];
} | {
type: 'updateAd';
info: ModerationLogPayloads['updateAd'];
} | {
type: 'deleteAd';
info: ModerationLogPayloads['deleteAd'];
});