Enhance: アカウント移行機能を使用したユーザーに対してのモデレーションの強化 (#719)
* fix * fix * fix * Feat: アカウント移行機能のモデレーションを行いやすくした * コミット忘れ * 文章を組み立てるのやめた * Fix test * Fix test * updateModerationNote から mergeModerationNote に * updateAccountMoveLogs から insertAccountMoveLog に --------- Co-authored-by: nenohi <nenohi@nenohi.net> Co-authored-by: nenohi <kimutipartylove@gmail.com>
This commit is contained in:
parent
2fe5bb0bb3
commit
6c732d1bfd
20
locales/index.d.ts
vendored
20
locales/index.d.ts
vendored
@ -1796,6 +1796,22 @@ export interface Locale extends ILocale {
|
|||||||
* モデログ
|
* モデログ
|
||||||
*/
|
*/
|
||||||
"moderationLogs": string;
|
"moderationLogs": string;
|
||||||
|
/**
|
||||||
|
* アカウント移行使用ログ
|
||||||
|
*/
|
||||||
|
"userAccountMoveLogs": string;
|
||||||
|
/**
|
||||||
|
* {from} が {to} にアカウントを移行しました
|
||||||
|
*/
|
||||||
|
"userAccountMoveLogsTitle": ParameterizedString<"from" | "to">;
|
||||||
|
/**
|
||||||
|
* 移行先のアカウントのID
|
||||||
|
*/
|
||||||
|
"movedToId": string;
|
||||||
|
/**
|
||||||
|
* 移行元のアカウントのID
|
||||||
|
*/
|
||||||
|
"moveFromId": string;
|
||||||
/**
|
/**
|
||||||
* {n}人が投稿
|
* {n}人が投稿
|
||||||
*/
|
*/
|
||||||
@ -4375,6 +4391,10 @@ export interface Locale extends ILocale {
|
|||||||
* このユーザーは新しいアカウントに移行しました:
|
* このユーザーは新しいアカウントに移行しました:
|
||||||
*/
|
*/
|
||||||
"accountMoved": string;
|
"accountMoved": string;
|
||||||
|
/**
|
||||||
|
* このユーザーは次のアカウントから移行されました:
|
||||||
|
*/
|
||||||
|
"accountMovedFrom": string;
|
||||||
/**
|
/**
|
||||||
* このアカウントは移行されています
|
* このアカウントは移行されています
|
||||||
*/
|
*/
|
||||||
|
@ -445,6 +445,10 @@ moderation: "モデレーション"
|
|||||||
moderationNote: "モデレーションノート"
|
moderationNote: "モデレーションノート"
|
||||||
addModerationNote: "モデレーションノートを追加する"
|
addModerationNote: "モデレーションノートを追加する"
|
||||||
moderationLogs: "モデログ"
|
moderationLogs: "モデログ"
|
||||||
|
userAccountMoveLogs: "アカウント移行使用ログ"
|
||||||
|
userAccountMoveLogsTitle: "{from} が {to} にアカウントを移行しました"
|
||||||
|
movedToId: "移行先のアカウントのID"
|
||||||
|
moveFromId: "移行元のアカウントのID"
|
||||||
nUsersMentioned: "{n}人が投稿"
|
nUsersMentioned: "{n}人が投稿"
|
||||||
securityKeyAndPasskey: "セキュリティキー・パスキー"
|
securityKeyAndPasskey: "セキュリティキー・パスキー"
|
||||||
securityKey: "セキュリティキー"
|
securityKey: "セキュリティキー"
|
||||||
@ -1089,6 +1093,7 @@ audioFiles: "音声"
|
|||||||
dataSaver: "データセーバー"
|
dataSaver: "データセーバー"
|
||||||
accountMigration: "アカウントの移行"
|
accountMigration: "アカウントの移行"
|
||||||
accountMoved: "このユーザーは新しいアカウントに移行しました:"
|
accountMoved: "このユーザーは新しいアカウントに移行しました:"
|
||||||
|
accountMovedFrom: "このユーザーは次のアカウントから移行されました:"
|
||||||
accountMovedShort: "このアカウントは移行されています"
|
accountMovedShort: "このアカウントは移行されています"
|
||||||
operationForbidden: "この操作はできません"
|
operationForbidden: "この操作はできません"
|
||||||
forceShowAds: "常に広告を表示する"
|
forceShowAds: "常に広告を表示する"
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
export class Useraccountmovelogs1724749627479 {
|
||||||
|
name = 'Useraccountmovelogs1724749627479'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "user_account_move_log" ("id" character varying(32) NOT NULL, "movedToId" character varying(32) NOT NULL, "movedFromId" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_8ffd4ae965a5e3a0fbf4b084212" PRIMARY KEY ("id")); COMMENT ON COLUMN "user_account_move_log"."createdAt" IS 'The created date of the UserIp.'`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_d5ee7d4d1b5e7a69d8855ab069" ON "user_account_move_log" ("movedToId") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_82930731d6390e7bb429a1938f" ON "user_account_move_log" ("movedFromId") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_account_move_log" ADD CONSTRAINT "FK_d5ee7d4d1b5e7a69d8855ab0696" FOREIGN KEY ("movedToId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_account_move_log" ADD CONSTRAINT "FK_82930731d6390e7bb429a1938f8" FOREIGN KEY ("movedFromId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_account_move_log" DROP CONSTRAINT "FK_82930731d6390e7bb429a1938f8"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user_account_move_log" DROP CONSTRAINT "FK_d5ee7d4d1b5e7a69d8855ab0696"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_82930731d6390e7bb429a1938f"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_d5ee7d4d1b5e7a69d8855ab069"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user_account_move_log"`);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
|
||||||
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
|
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UserAccountMoveLogRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
||||||
|
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
@ -48,6 +48,15 @@ export class AccountMoveService {
|
|||||||
@Inject(DI.instancesRepository)
|
@Inject(DI.instancesRepository)
|
||||||
private instancesRepository: InstancesRepository,
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
private userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userAccountMoveLogRepository)
|
||||||
|
private userAccountMoveLogRepository: UserAccountMoveLogRepository,
|
||||||
|
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private apPersonService: ApPersonService,
|
private apPersonService: ApPersonService,
|
||||||
@ -119,6 +128,8 @@ export class AccountMoveService {
|
|||||||
this.copyBlocking(src, dst),
|
this.copyBlocking(src, dst),
|
||||||
this.copyMutings(src, dst),
|
this.copyMutings(src, dst),
|
||||||
this.updateLists(src, dst),
|
this.updateLists(src, dst),
|
||||||
|
this.mergeModerationNote(src, dst),
|
||||||
|
this.insertAccountMoveLog(src, dst),
|
||||||
]);
|
]);
|
||||||
} catch {
|
} catch {
|
||||||
/* skip if any error happens */
|
/* skip if any error happens */
|
||||||
@ -256,6 +267,32 @@ export class AccountMoveService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async mergeModerationNote(src: ThinUser, dst: MiUser): Promise<void> {
|
||||||
|
const srcprofile = await this.userProfilesRepository.findOneBy({ userId: src.id });
|
||||||
|
const dstprofile = await this.userProfilesRepository.findOneBy({ userId: dst.id });
|
||||||
|
|
||||||
|
if (!srcprofile || !dstprofile) return;
|
||||||
|
|
||||||
|
await this.userProfilesRepository.update({ userId: dst.id }, {
|
||||||
|
moderationNote: srcprofile.moderationNote + '\n' + dstprofile.moderationNote,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.userProfilesRepository.update({ userId: src.id }, {
|
||||||
|
moderationNote: srcprofile.moderationNote + '\n' + dstprofile.moderationNote,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async insertAccountMoveLog(src: ThinUser, dst: MiUser): Promise<void> {
|
||||||
|
await this.userAccountMoveLogRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
movedToId: dst.id,
|
||||||
|
movedFromId: src.id,
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: MiUser): Promise<void> {
|
private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: MiUser): Promise<void> {
|
||||||
if (localFollowerIds.length === 0) return;
|
if (localFollowerIds.length === 0) return;
|
||||||
|
@ -101,6 +101,7 @@ import { HashtagEntityService } from './entities/HashtagEntityService.js';
|
|||||||
import { InstanceEntityService } from './entities/InstanceEntityService.js';
|
import { InstanceEntityService } from './entities/InstanceEntityService.js';
|
||||||
import { InviteCodeEntityService } from './entities/InviteCodeEntityService.js';
|
import { InviteCodeEntityService } from './entities/InviteCodeEntityService.js';
|
||||||
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
|
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
|
||||||
|
import { UserAccountMoveLogEntityService } from './entities/UserAccountMoveLogEntityService.js';
|
||||||
import { MutingEntityService } from './entities/MutingEntityService.js';
|
import { MutingEntityService } from './entities/MutingEntityService.js';
|
||||||
import { RenoteMutingEntityService } from './entities/RenoteMutingEntityService.js';
|
import { RenoteMutingEntityService } from './entities/RenoteMutingEntityService.js';
|
||||||
import { NoteEntityService } from './entities/NoteEntityService.js';
|
import { NoteEntityService } from './entities/NoteEntityService.js';
|
||||||
@ -242,6 +243,7 @@ const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useEx
|
|||||||
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
|
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
|
||||||
const $InviteCodeEntityService: Provider = { provide: 'InviteCodeEntityService', useExisting: InviteCodeEntityService };
|
const $InviteCodeEntityService: Provider = { provide: 'InviteCodeEntityService', useExisting: InviteCodeEntityService };
|
||||||
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
|
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
|
||||||
|
const $UserAccountMoveLogEntityService: Provider = { provide: 'UserAccountMoveLogEntityService', useExisting: UserAccountMoveLogEntityService };
|
||||||
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
|
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
|
||||||
const $RenoteMutingEntityService: Provider = { provide: 'RenoteMutingEntityService', useExisting: RenoteMutingEntityService };
|
const $RenoteMutingEntityService: Provider = { provide: 'RenoteMutingEntityService', useExisting: RenoteMutingEntityService };
|
||||||
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
|
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
|
||||||
@ -382,6 +384,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
InstanceEntityService,
|
InstanceEntityService,
|
||||||
InviteCodeEntityService,
|
InviteCodeEntityService,
|
||||||
ModerationLogEntityService,
|
ModerationLogEntityService,
|
||||||
|
UserAccountMoveLogEntityService,
|
||||||
MutingEntityService,
|
MutingEntityService,
|
||||||
RenoteMutingEntityService,
|
RenoteMutingEntityService,
|
||||||
NoteEntityService,
|
NoteEntityService,
|
||||||
@ -518,6 +521,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$InstanceEntityService,
|
$InstanceEntityService,
|
||||||
$InviteCodeEntityService,
|
$InviteCodeEntityService,
|
||||||
$ModerationLogEntityService,
|
$ModerationLogEntityService,
|
||||||
|
$UserAccountMoveLogEntityService,
|
||||||
$MutingEntityService,
|
$MutingEntityService,
|
||||||
$RenoteMutingEntityService,
|
$RenoteMutingEntityService,
|
||||||
$NoteEntityService,
|
$NoteEntityService,
|
||||||
@ -654,6 +658,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
InstanceEntityService,
|
InstanceEntityService,
|
||||||
InviteCodeEntityService,
|
InviteCodeEntityService,
|
||||||
ModerationLogEntityService,
|
ModerationLogEntityService,
|
||||||
|
UserAccountMoveLogEntityService,
|
||||||
MutingEntityService,
|
MutingEntityService,
|
||||||
RenoteMutingEntityService,
|
RenoteMutingEntityService,
|
||||||
NoteEntityService,
|
NoteEntityService,
|
||||||
@ -789,6 +794,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||||||
$InstanceEntityService,
|
$InstanceEntityService,
|
||||||
$InviteCodeEntityService,
|
$InviteCodeEntityService,
|
||||||
$ModerationLogEntityService,
|
$ModerationLogEntityService,
|
||||||
|
$UserAccountMoveLogEntityService,
|
||||||
$MutingEntityService,
|
$MutingEntityService,
|
||||||
$RenoteMutingEntityService,
|
$RenoteMutingEntityService,
|
||||||
$NoteEntityService,
|
$NoteEntityService,
|
||||||
|
@ -682,10 +682,7 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
// まずサーバー内で検索して様子見
|
// まずサーバー内で検索して様子見
|
||||||
let dst = await this.fetchPerson(src.movedToUri);
|
let dst = await this.fetchPerson(src.movedToUri);
|
||||||
|
|
||||||
if (dst && this.userEntityService.isLocalUser(dst)) {
|
if (dst) {
|
||||||
// targetがローカルユーザーだった場合データベースから引っ張ってくる
|
|
||||||
dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as MiLocalUser;
|
|
||||||
} else if (dst) {
|
|
||||||
if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move';
|
if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move';
|
||||||
|
|
||||||
// targetを見つけたことがあるならtargetをupdatePersonする
|
// targetを見つけたことがあるならtargetをupdatePersonする
|
||||||
@ -702,13 +699,15 @@ export class ApPersonService implements OnModuleInit {
|
|||||||
dst = await this.resolvePerson(src.movedToUri);
|
dst = await this.resolvePerson(src.movedToUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dst.movedToUri === dst.uri) return 'skip: movedTo itself (dst)'; // ???
|
const dstUri = this.userEntityService.getUserUri(dst);
|
||||||
if (src.movedToUri !== dst.uri) return 'skip: missmatch uri'; // ???
|
const srcUri = this.userEntityService.getUserUri(src);
|
||||||
if (dst.movedToUri === src.uri) return 'skip: dst.movedToUri === src.uri';
|
if (dst.movedToUri === dstUri) return 'skip: movedTo itself (dst)'; // ???
|
||||||
|
if (src.movedToUri !== dstUri) return 'skip: missmatch uri'; // ???
|
||||||
|
if (dst.movedToUri === srcUri) return 'skip: dst.movedToUri === src.uri';
|
||||||
if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
|
if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
|
||||||
return 'skip: dst.alsoKnownAs is empty';
|
return 'skip: dst.alsoKnownAs is empty';
|
||||||
}
|
}
|
||||||
if (!dst.alsoKnownAs.includes(src.uri)) {
|
if (!dst.alsoKnownAs.includes(srcUri)) {
|
||||||
return 'skip: alsoKnownAs does not include from.uri';
|
return 'skip: alsoKnownAs does not include from.uri';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { MiUserAccountMoveLog, UserAccountMoveLogRepository } from '@/models/_.js';
|
||||||
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserAccountMoveLogEntityService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.userAccountMoveLogRepository)
|
||||||
|
private userAccountMoveLogRepository: UserAccountMoveLogRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async pack(
|
||||||
|
src: MiUserAccountMoveLog['id'] | MiUserAccountMoveLog,
|
||||||
|
me: { id: MiUser['id'] } | null | undefined,
|
||||||
|
) : Promise<Packed<'UserAccountMoveLog'>> {
|
||||||
|
const log = typeof src === 'object' ? src : await this.userAccountMoveLogRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return await awaitAll({
|
||||||
|
id: log.id,
|
||||||
|
createdAt: this.idService.parse(log.id).date.toISOString(),
|
||||||
|
movedFromId: log.movedFromId,
|
||||||
|
movedFrom: this.userEntityService.pack(log.movedFrom ?? log.movedFromId, me, {
|
||||||
|
schema: 'UserDetailed',
|
||||||
|
}),
|
||||||
|
movedToId: log.movedToId,
|
||||||
|
movedTo: this.userEntityService.pack(log.movedTo ?? log.movedToId, me, {
|
||||||
|
schema: 'UserDetailed',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packMany(
|
||||||
|
reports: (MiUserAccountMoveLog['id'] | MiUserAccountMoveLog)[],
|
||||||
|
me: { id: MiUser['id'] } | null | undefined,
|
||||||
|
) : Promise<Packed<'UserAccountMoveLog'>[]> {
|
||||||
|
return (await Promise.allSettled(reports.map(x => this.pack(x, me))))
|
||||||
|
.filter(result => result.status === 'fulfilled')
|
||||||
|
.map(result => (result as PromiseFulfilledResult<Packed<'UserAccountMoveLog'>>).value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ export const DI = {
|
|||||||
userListMembershipsRepository: Symbol('userListMembershipsRepository'),
|
userListMembershipsRepository: Symbol('userListMembershipsRepository'),
|
||||||
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
||||||
userIpsRepository: Symbol('userIpsRepository'),
|
userIpsRepository: Symbol('userIpsRepository'),
|
||||||
|
userAccountMoveLogRepository: Symbol('userAccountMoveLogRepository'),
|
||||||
usedUsernamesRepository: Symbol('usedUsernamesRepository'),
|
usedUsernamesRepository: Symbol('usedUsernamesRepository'),
|
||||||
followingsRepository: Symbol('followingsRepository'),
|
followingsRepository: Symbol('followingsRepository'),
|
||||||
followRequestsRepository: Symbol('followRequestsRepository'),
|
followRequestsRepository: Symbol('followRequestsRepository'),
|
||||||
|
@ -37,6 +37,7 @@ import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
|
|||||||
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
|
||||||
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js';
|
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js';
|
||||||
import { packedUserListMembershipSchema, packedUserListSchema } from '@/models/json-schema/user-list.js';
|
import { packedUserListMembershipSchema, packedUserListSchema } from '@/models/json-schema/user-list.js';
|
||||||
|
import { packedUserAccountMoveLogSchema } from '@/models/json-schema/user-account-move-log.js';
|
||||||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||||
import {
|
import {
|
||||||
@ -71,6 +72,7 @@ export const refs = {
|
|||||||
|
|
||||||
UserList: packedUserListSchema,
|
UserList: packedUserListSchema,
|
||||||
UserListMembership: packedUserListMembershipSchema,
|
UserListMembership: packedUserListMembershipSchema,
|
||||||
|
UserAccountMoveLog: packedUserAccountMoveLogSchema,
|
||||||
Ad: packedAdSchema,
|
Ad: packedAdSchema,
|
||||||
Announcement: packedAnnouncementSchema,
|
Announcement: packedAnnouncementSchema,
|
||||||
App: packedAppSchema,
|
App: packedAppSchema,
|
||||||
|
@ -73,6 +73,7 @@ import {
|
|||||||
MiUserProfile,
|
MiUserProfile,
|
||||||
MiUserPublickey,
|
MiUserPublickey,
|
||||||
MiUserSecurityKey,
|
MiUserSecurityKey,
|
||||||
|
MiUserAccountMoveLog,
|
||||||
MiWebhook,
|
MiWebhook,
|
||||||
MiBubbleGameRecord,
|
MiBubbleGameRecord,
|
||||||
MiReversiGame,
|
MiReversiGame,
|
||||||
@ -200,6 +201,12 @@ const $userListMembershipsRepository: Provider = {
|
|||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $userAccountMoveLogRepository: Provider = {
|
||||||
|
provide: DI.userAccountMoveLogRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiUserAccountMoveLog),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $userNotePiningsRepository: Provider = {
|
const $userNotePiningsRepository: Provider = {
|
||||||
provide: DI.userNotePiningsRepository,
|
provide: DI.userNotePiningsRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining),
|
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining),
|
||||||
@ -524,6 +531,7 @@ const $abuseReportResolversRepository: Provider = {
|
|||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
$userListFavoritesRepository,
|
$userListFavoritesRepository,
|
||||||
$userListMembershipsRepository,
|
$userListMembershipsRepository,
|
||||||
|
$userAccountMoveLogRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
$usedUsernamesRepository,
|
$usedUsernamesRepository,
|
||||||
@ -596,6 +604,7 @@ const $abuseReportResolversRepository: Provider = {
|
|||||||
$userListsRepository,
|
$userListsRepository,
|
||||||
$userListFavoritesRepository,
|
$userListFavoritesRepository,
|
||||||
$userListMembershipsRepository,
|
$userListMembershipsRepository,
|
||||||
|
$userAccountMoveLogRepository,
|
||||||
$userNotePiningsRepository,
|
$userNotePiningsRepository,
|
||||||
$userIpsRepository,
|
$userIpsRepository,
|
||||||
$usedUsernamesRepository,
|
$usedUsernamesRepository,
|
||||||
|
35
packages/backend/src/models/UserAccountMoveLog.ts
Normal file
35
packages/backend/src/models/UserAccountMoveLog.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Entity, Index, Column, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
import { MiUser } from './User.js';
|
||||||
|
|
||||||
|
@Entity('user_account_move_log')
|
||||||
|
export class MiUserAccountMoveLog {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column(id())
|
||||||
|
public movedToId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public movedTo: MiUser | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column(id())
|
||||||
|
public movedFromId: MiUser['id'];
|
||||||
|
|
||||||
|
@ManyToOne(type => MiUser, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public movedFrom: MiUser | null;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
comment: 'The created date of the UserIp.',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
})
|
||||||
|
public createdAt: Date;
|
||||||
|
}
|
@ -63,6 +63,7 @@ import { MiUserProfile } from '@/models/UserProfile.js';
|
|||||||
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||||
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
||||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||||
|
import { MiUserAccountMoveLog } from '@/models/UserAccountMoveLog.js';
|
||||||
import { MiWebhook } from '@/models/Webhook.js';
|
import { MiWebhook } from '@/models/Webhook.js';
|
||||||
import { MiChannel } from '@/models/Channel.js';
|
import { MiChannel } from '@/models/Channel.js';
|
||||||
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
||||||
@ -146,6 +147,7 @@ export {
|
|||||||
MiUserMemo,
|
MiUserMemo,
|
||||||
MiBubbleGameRecord,
|
MiBubbleGameRecord,
|
||||||
MiReversiGame,
|
MiReversiGame,
|
||||||
|
MiUserAccountMoveLog,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbuseReportResolversRepository = Repository<MiAbuseReportResolver>;
|
export type AbuseReportResolversRepository = Repository<MiAbuseReportResolver>;
|
||||||
@ -208,6 +210,7 @@ export type UserPendingsRepository = Repository<MiUserPending>;
|
|||||||
export type UserProfilesRepository = Repository<MiUserProfile>;
|
export type UserProfilesRepository = Repository<MiUserProfile>;
|
||||||
export type UserPublickeysRepository = Repository<MiUserPublickey>;
|
export type UserPublickeysRepository = Repository<MiUserPublickey>;
|
||||||
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey>;
|
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey>;
|
||||||
|
export type UserAccountMoveLogRepository = Repository<MiUserAccountMoveLog>;
|
||||||
export type WebhooksRepository = Repository<MiWebhook>;
|
export type WebhooksRepository = Repository<MiWebhook>;
|
||||||
export type ChannelsRepository = Repository<MiChannel>;
|
export type ChannelsRepository = Repository<MiChannel>;
|
||||||
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation>;
|
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation>;
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
export const packedUserAccountMoveLogSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
example: 'xxxxxxxxxx',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
movedToId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
movedTo: {
|
||||||
|
type: 'object',
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
movedFromId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
movedFrom: {
|
||||||
|
type: 'object',
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
@ -73,6 +73,7 @@ import { MiUserPending } from '@/models/UserPending.js';
|
|||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
import { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||||
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
||||||
|
import { MiUserAccountMoveLog } from '@/models/UserAccountMoveLog.js';
|
||||||
import { MiWebhook } from '@/models/Webhook.js';
|
import { MiWebhook } from '@/models/Webhook.js';
|
||||||
import { MiChannel } from '@/models/Channel.js';
|
import { MiChannel } from '@/models/Channel.js';
|
||||||
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
||||||
@ -153,6 +154,7 @@ export const entities = [
|
|||||||
MiUserListMembership,
|
MiUserListMembership,
|
||||||
MiUserNotePining,
|
MiUserNotePining,
|
||||||
MiUserSecurityKey,
|
MiUserSecurityKey,
|
||||||
|
MiUserAccountMoveLog,
|
||||||
MiUsedUsername,
|
MiUsedUsername,
|
||||||
MiFollowing,
|
MiFollowing,
|
||||||
MiFollowRequest,
|
MiFollowRequest,
|
||||||
|
@ -77,6 +77,7 @@ import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-ab
|
|||||||
import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
|
import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
|
||||||
import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
|
import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
|
||||||
import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
|
import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
|
||||||
|
import * as ep___admin_showUserAccountMoveLogs from './endpoints/admin/show-user-account-move-logs.js';
|
||||||
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
||||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||||
@ -468,6 +469,7 @@ const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abu
|
|||||||
const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
|
const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
|
||||||
const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
|
const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
|
||||||
const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default };
|
const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default };
|
||||||
|
const $admin_showUserAccountMoveLogs: Provider = { provide: 'ep:admin/show-user-account-move-logs', useClass: ep___admin_showUserAccountMoveLogs.default };
|
||||||
const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default };
|
const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default };
|
||||||
const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
|
const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
|
||||||
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
|
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
|
||||||
@ -863,6 +865,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_sendEmail,
|
$admin_sendEmail,
|
||||||
$admin_serverInfo,
|
$admin_serverInfo,
|
||||||
$admin_showModerationLogs,
|
$admin_showModerationLogs,
|
||||||
|
$admin_showUserAccountMoveLogs,
|
||||||
$admin_showUser,
|
$admin_showUser,
|
||||||
$admin_showUsers,
|
$admin_showUsers,
|
||||||
$admin_suspendUser,
|
$admin_suspendUser,
|
||||||
@ -1252,6 +1255,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||||||
$admin_sendEmail,
|
$admin_sendEmail,
|
||||||
$admin_serverInfo,
|
$admin_serverInfo,
|
||||||
$admin_showModerationLogs,
|
$admin_showModerationLogs,
|
||||||
|
$admin_showUserAccountMoveLogs,
|
||||||
$admin_showUser,
|
$admin_showUser,
|
||||||
$admin_showUsers,
|
$admin_showUsers,
|
||||||
$admin_suspendUser,
|
$admin_suspendUser,
|
||||||
|
@ -77,6 +77,7 @@ import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-ab
|
|||||||
import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
|
import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
|
||||||
import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
|
import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
|
||||||
import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
|
import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
|
||||||
|
import * as ep___admin_showUserAccountMoveLogs from './endpoints/admin/show-user-account-move-logs.js';
|
||||||
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
import * as ep___admin_showUser from './endpoints/admin/show-user.js';
|
||||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||||
@ -466,6 +467,7 @@ const eps = [
|
|||||||
['admin/send-email', ep___admin_sendEmail],
|
['admin/send-email', ep___admin_sendEmail],
|
||||||
['admin/server-info', ep___admin_serverInfo],
|
['admin/server-info', ep___admin_serverInfo],
|
||||||
['admin/show-moderation-logs', ep___admin_showModerationLogs],
|
['admin/show-moderation-logs', ep___admin_showModerationLogs],
|
||||||
|
['admin/show-user-account-move-logs', ep___admin_showUserAccountMoveLogs],
|
||||||
['admin/show-user', ep___admin_showUser],
|
['admin/show-user', ep___admin_showUser],
|
||||||
['admin/show-users', ep___admin_showUsers],
|
['admin/show-users', ep___admin_showUsers],
|
||||||
['admin/suspend-user', ep___admin_suspendUser],
|
['admin/suspend-user', ep___admin_suspendUser],
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { UserAccountMoveLogRepository } from '@/models/_.js';
|
||||||
|
import { UserAccountMoveLogEntityService } from '@/core/entities/UserAccountMoveLogEntityService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
kind: 'read:admin:show-account-move-log',
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
movedToId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
movedTo: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
},
|
||||||
|
movedFromId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
movedFrom: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserDetailed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
movedFromId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
movedToId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.userAccountMoveLogRepository)
|
||||||
|
private userAccountMoveLogRepository: UserAccountMoveLogRepository,
|
||||||
|
|
||||||
|
private userAccountMoveLogEntityService: UserAccountMoveLogEntityService,
|
||||||
|
private queryService: QueryService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const query = this.queryService.makePaginationQuery(this.userAccountMoveLogRepository.createQueryBuilder('accountMoveLogs'), ps.sinceId, ps.untilId);
|
||||||
|
|
||||||
|
if (ps.movedFromId != null) {
|
||||||
|
query.andWhere('accountMoveLogs.movedFromId = :movedFromId', { movedFromId: ps.movedFromId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.movedToId != null) {
|
||||||
|
query.andWhere('accountMoveLogs.movedToId = :movedToId', { movedToId: ps.movedToId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountMoveLogs = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
|
return await this.userAccountMoveLogEntityService.packMany(accountMoveLogs, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="user" :class="$style.root">
|
<div v-if="user" :class="$style.root">
|
||||||
<i class="ti ti-plane-departure" style="margin-right: 8px;"></i>
|
<i class="ti ti-plane-departure" style="margin-right: 8px;"></i>
|
||||||
{{ i18n.ts.accountMoved }}
|
<span v-if="movedTo">{{ i18n.ts.accountMoved }}</span>
|
||||||
|
<span v-if="movedFrom">{{ i18n.ts.accountMovedFrom }}</span>
|
||||||
<MkMention :class="$style.link" :username="user.username" :host="user.host ?? localHost"/>
|
<MkMention :class="$style.link" :username="user.username" :host="user.host ?? localHost"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -22,10 +23,11 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
|||||||
const user = ref<Misskey.entities.UserLite>();
|
const user = ref<Misskey.entities.UserLite>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
movedTo: string; // user id
|
movedTo?: string; // user id
|
||||||
|
movedFrom?: string; // user id
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u);
|
misskeyApi('users/show', { userId: props.movedTo ?? props.movedFrom }).then(u => user.value = u);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -156,6 +156,11 @@ const menuDef = computed(() => [{
|
|||||||
text: i18n.ts.moderationLogs,
|
text: i18n.ts.moderationLogs,
|
||||||
to: '/admin/modlog',
|
to: '/admin/modlog',
|
||||||
active: currentPage.value?.route.name === 'modlog',
|
active: currentPage.value?.route.name === 'modlog',
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-list-search',
|
||||||
|
text: i18n.ts.userAccountMoveLogs,
|
||||||
|
to: '/admin/useraccountmovelog',
|
||||||
|
active: currentPage.value?.route.name === 'useraccountmovelog',
|
||||||
}],
|
}],
|
||||||
}, {
|
}, {
|
||||||
title: i18n.ts.settings,
|
title: i18n.ts.settings,
|
||||||
|
98
packages/frontend/src/pages/admin/useraccountmovelog.vue
Normal file
98
packages/frontend/src/pages/admin/useraccountmovelog.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
<MkSpacer :contentMax="900">
|
||||||
|
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
||||||
|
<MkInput v-model="movedFromId" style="margin: 0; flex: 1;">
|
||||||
|
<template #label> {{ i18n.ts.moveFromId }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="movedToId" style="margin: 0; flex: 1;">
|
||||||
|
<template #label> {{ i18n.ts.movedToId }}</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkFolder v-for="item in items" :key="item.id">
|
||||||
|
<template #label>
|
||||||
|
{{ i18n.tsx.userAccountMoveLogsTitle({
|
||||||
|
from: '@' + item.movedFrom.username + (item.movedFrom.host ? `@${item.movedFrom.host}` : ''),
|
||||||
|
to: '@' + item.movedTo.username + (item.movedTo.host ? `@${item.movedTo.host}` : '')
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<div :class="$style.card">
|
||||||
|
<MkA :to="userPage(item.movedFrom)" :class="$style.cardContent">
|
||||||
|
<MkAvatar :user="item.movedFrom" :class="$style.avatar" link/>
|
||||||
|
<MkAcct :user="item.movedFrom"/>
|
||||||
|
</MkA>
|
||||||
|
→
|
||||||
|
<MkA :to="userPage(item.movedTo)" :class="$style.cardContent">
|
||||||
|
<MkAvatar :user="item.movedTo" :class="$style.avatar"/>
|
||||||
|
<MkAcct :user="item.movedTo"/>
|
||||||
|
</MkA>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
</MkPagination>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, shallowRef, ref } from 'vue';
|
||||||
|
import XHeader from './_header_.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import { userPage } from '@/filters/user.js';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
|
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
|
const movedToId = ref('');
|
||||||
|
const movedFromId = ref('');
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
endpoint: 'admin/show-user-account-move-logs' as const,
|
||||||
|
limit: 30,
|
||||||
|
params: computed(() => ({
|
||||||
|
movedFromId: movedFromId.value === '' ? null : movedFromId.value,
|
||||||
|
movedToId: movedToId.value === '' ? null : movedToId.value,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
definePageMetadata(() => ({
|
||||||
|
title: i18n.ts.userAccountMoveLogs,
|
||||||
|
icon: 'ti ti-list-search',
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--margin);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: var(--margin);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardContent{
|
||||||
|
display: flex;
|
||||||
|
gap: var(--margin);
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<div class="profile _gaps">
|
<div class="profile _gaps">
|
||||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||||
|
<MkAccountMoved v-if="movedFromLog" :movedFrom="movedFromLog[0]?.movedFromId"/>
|
||||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
|
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
|
||||||
|
|
||||||
<div :key="user.id" class="main _panel">
|
<div :key="user.id" class="main _panel">
|
||||||
@ -280,6 +281,7 @@ const memoDraft = ref(props.user.memo);
|
|||||||
const isEditingMemo = ref(false);
|
const isEditingMemo = ref(false);
|
||||||
const moderationNote = ref(props.user.moderationNote);
|
const moderationNote = ref(props.user.moderationNote);
|
||||||
const editModerationNote = ref(false);
|
const editModerationNote = ref(false);
|
||||||
|
const movedFromLog = ref<null | {movedFromId:string;}[]>(null);
|
||||||
|
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
|
await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
|
||||||
@ -301,6 +303,15 @@ function menu(ev: MouseEvent) {
|
|||||||
os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
|
os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchMovedFromLog() {
|
||||||
|
if (!props.user.id) {
|
||||||
|
movedFromLog.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
movedFromLog.value = await misskeyApi('admin/show-user-account-move-logs', { movedToId: props.user.id });
|
||||||
|
}
|
||||||
|
|
||||||
function parallaxLoop() {
|
function parallaxLoop() {
|
||||||
parallaxAnimationId.value = window.requestAnimationFrame(parallaxLoop);
|
parallaxAnimationId.value = window.requestAnimationFrame(parallaxLoop);
|
||||||
parallax();
|
parallax();
|
||||||
@ -377,6 +388,9 @@ function buildSkebStatus(): string {
|
|||||||
watch([props.user], () => {
|
watch([props.user], () => {
|
||||||
memoDraft.value = props.user.memo;
|
memoDraft.value = props.user.memo;
|
||||||
fetchSkebStatus();
|
fetchSkebStatus();
|
||||||
|
if ($i?.isModerator) {
|
||||||
|
fetchMovedFromLog();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -395,6 +409,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchSkebStatus();
|
fetchSkebStatus();
|
||||||
|
if ($i?.isModerator) {
|
||||||
|
fetchMovedFromLog();
|
||||||
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
adjustMemoTextarea();
|
adjustMemoTextarea();
|
||||||
});
|
});
|
||||||
|
@ -426,6 +426,10 @@ const routes: RouteDef[] = [{
|
|||||||
path: '/modlog',
|
path: '/modlog',
|
||||||
name: 'modlog',
|
name: 'modlog',
|
||||||
component: page(() => import('@/pages/admin/modlog.vue')),
|
component: page(() => import('@/pages/admin/modlog.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/useraccountmovelog',
|
||||||
|
name: 'useraccountmovelog',
|
||||||
|
component: page(() => import('@/pages/admin/useraccountmovelog.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
@ -337,6 +337,12 @@ type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
|
type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminShowUserAccountMoveLogsRequest = operations['admin___show-user-account-move-logs']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type AdminShowUserAccountMoveLogsResponse = operations['admin___show-user-account-move-logs']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
|
type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
@ -1292,6 +1298,8 @@ declare namespace entities {
|
|||||||
AdminServerInfoResponse,
|
AdminServerInfoResponse,
|
||||||
AdminShowModerationLogsRequest,
|
AdminShowModerationLogsRequest,
|
||||||
AdminShowModerationLogsResponse,
|
AdminShowModerationLogsResponse,
|
||||||
|
AdminShowUserAccountMoveLogsRequest,
|
||||||
|
AdminShowUserAccountMoveLogsResponse,
|
||||||
AdminShowUserRequest,
|
AdminShowUserRequest,
|
||||||
AdminShowUserResponse,
|
AdminShowUserResponse,
|
||||||
AdminShowUsersRequest,
|
AdminShowUsersRequest,
|
||||||
@ -1789,6 +1797,7 @@ declare namespace entities {
|
|||||||
User,
|
User,
|
||||||
UserList,
|
UserList,
|
||||||
UserListMembership,
|
UserListMembership,
|
||||||
|
UserAccountMoveLog,
|
||||||
Ad,
|
Ad,
|
||||||
Announcement,
|
Announcement,
|
||||||
App,
|
App,
|
||||||
@ -2758,7 +2767,7 @@ type PagesUpdateRequest = operations['pages___update']['requestBody']['content']
|
|||||||
function parse(acct: string): Acct;
|
function parse(acct: string): Acct;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unset-user-mutual-link", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-account-move-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unset-user-mutual-link", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
||||||
@ -3057,6 +3066,9 @@ function toString_2(acct: Acct): string;
|
|||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type User = components['schemas']['User'];
|
type User = components['schemas']['User'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type UserAccountMoveLog = components['schemas']['UserAccountMoveLog'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type UserDetailed = components['schemas']['UserDetailed'];
|
type UserDetailed = components['schemas']['UserDetailed'];
|
||||||
|
|
||||||
|
@ -785,6 +785,17 @@ declare module '../api.js' {
|
|||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:show-account-move-log*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/show-user-account-move-logs', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -94,6 +94,8 @@ import type {
|
|||||||
AdminServerInfoResponse,
|
AdminServerInfoResponse,
|
||||||
AdminShowModerationLogsRequest,
|
AdminShowModerationLogsRequest,
|
||||||
AdminShowModerationLogsResponse,
|
AdminShowModerationLogsResponse,
|
||||||
|
AdminShowUserAccountMoveLogsRequest,
|
||||||
|
AdminShowUserAccountMoveLogsResponse,
|
||||||
AdminShowUserRequest,
|
AdminShowUserRequest,
|
||||||
AdminShowUserResponse,
|
AdminShowUserResponse,
|
||||||
AdminShowUsersRequest,
|
AdminShowUsersRequest,
|
||||||
@ -655,6 +657,7 @@ export type Endpoints = {
|
|||||||
'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse };
|
'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse };
|
||||||
'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse };
|
'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse };
|
||||||
'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse };
|
'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse };
|
||||||
|
'admin/show-user-account-move-logs': { req: AdminShowUserAccountMoveLogsRequest; res: AdminShowUserAccountMoveLogsResponse };
|
||||||
'admin/show-user': { req: AdminShowUserRequest; res: AdminShowUserResponse };
|
'admin/show-user': { req: AdminShowUserRequest; res: AdminShowUserResponse };
|
||||||
'admin/show-users': { req: AdminShowUsersRequest; res: AdminShowUsersResponse };
|
'admin/show-users': { req: AdminShowUsersRequest; res: AdminShowUsersResponse };
|
||||||
'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
|
'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
|
||||||
|
@ -97,6 +97,8 @@ export type AdminSendEmailRequest = operations['admin___send-email']['requestBod
|
|||||||
export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json'];
|
export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json'];
|
||||||
export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json'];
|
export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json'];
|
||||||
export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
|
export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
|
||||||
|
export type AdminShowUserAccountMoveLogsRequest = operations['admin___show-user-account-move-logs']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminShowUserAccountMoveLogsResponse = operations['admin___show-user-account-move-logs']['responses']['200']['content']['application/json'];
|
||||||
export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
|
export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json'];
|
export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json'];
|
||||||
export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json'];
|
export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json'];
|
||||||
|
@ -9,6 +9,7 @@ export type UserDetailed = components['schemas']['UserDetailed'];
|
|||||||
export type User = components['schemas']['User'];
|
export type User = components['schemas']['User'];
|
||||||
export type UserList = components['schemas']['UserList'];
|
export type UserList = components['schemas']['UserList'];
|
||||||
export type UserListMembership = components['schemas']['UserListMembership'];
|
export type UserListMembership = components['schemas']['UserListMembership'];
|
||||||
|
export type UserAccountMoveLog = components['schemas']['UserAccountMoveLog'];
|
||||||
export type Ad = components['schemas']['Ad'];
|
export type Ad = components['schemas']['Ad'];
|
||||||
export type Announcement = components['schemas']['Announcement'];
|
export type Announcement = components['schemas']['Announcement'];
|
||||||
export type App = components['schemas']['App'];
|
export type App = components['schemas']['App'];
|
||||||
|
@ -652,6 +652,15 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
post: operations['admin___show-moderation-logs'];
|
post: operations['admin___show-moderation-logs'];
|
||||||
};
|
};
|
||||||
|
'/admin/show-user-account-move-logs': {
|
||||||
|
/**
|
||||||
|
* admin/show-user-account-move-logs
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:show-account-move-log*
|
||||||
|
*/
|
||||||
|
post: operations['admin___show-user-account-move-logs'];
|
||||||
|
};
|
||||||
'/admin/show-user': {
|
'/admin/show-user': {
|
||||||
/**
|
/**
|
||||||
* admin/show-user
|
* admin/show-user
|
||||||
@ -4078,6 +4087,21 @@ export type components = {
|
|||||||
user: components['schemas']['UserLite'];
|
user: components['schemas']['UserLite'];
|
||||||
withReplies: boolean;
|
withReplies: boolean;
|
||||||
};
|
};
|
||||||
|
UserAccountMoveLog: {
|
||||||
|
/**
|
||||||
|
* Format: id
|
||||||
|
* @example xxxxxxxxxx
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: id */
|
||||||
|
movedToId: string;
|
||||||
|
movedTo: components['schemas']['UserDetailed'];
|
||||||
|
/** Format: id */
|
||||||
|
movedFromId: string;
|
||||||
|
movedFrom: components['schemas']['UserDetailed'];
|
||||||
|
};
|
||||||
Ad: {
|
Ad: {
|
||||||
/**
|
/**
|
||||||
* Format: id
|
* Format: id
|
||||||
@ -9440,6 +9464,79 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/show-user-account-move-logs
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *read:admin:show-account-move-log*
|
||||||
|
*/
|
||||||
|
'admin___show-user-account-move-logs': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** @default 10 */
|
||||||
|
limit?: number;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
sinceId?: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
untilId?: string;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
movedFromId?: string | null;
|
||||||
|
/** Format: misskey:id */
|
||||||
|
movedToId?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: id */
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: id */
|
||||||
|
movedToId: string;
|
||||||
|
movedTo: components['schemas']['UserDetailed'];
|
||||||
|
/** Format: id */
|
||||||
|
movedFromId: string;
|
||||||
|
movedFrom: components['schemas']['UserDetailed'];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/show-user
|
* admin/show-user
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -57,6 +57,7 @@ export const permissions = [
|
|||||||
'write:admin:send-email',
|
'write:admin:send-email',
|
||||||
'read:admin:server-info',
|
'read:admin:server-info',
|
||||||
'read:admin:show-moderation-log',
|
'read:admin:show-moderation-log',
|
||||||
|
'read:admin:show-account-move-log',
|
||||||
'read:admin:show-user',
|
'read:admin:show-user',
|
||||||
'read:admin:show-users',
|
'read:admin:show-users',
|
||||||
'write:admin:suspend-user',
|
'write:admin:suspend-user',
|
||||||
|
Loading…
Reference in New Issue
Block a user