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:
まっちゃてぃー。 2024-09-16 22:18:41 +09:00 committed by GitHub
parent 2fe5bb0bb3
commit 6c732d1bfd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 593 additions and 13 deletions

View file

@ -10,7 +10,7 @@ import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.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 { IdService } from '@/core/IdService.js';
@ -48,6 +48,15 @@ export class AccountMoveService {
@Inject(DI.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 idService: IdService,
private apPersonService: ApPersonService,
@ -119,6 +128,8 @@ export class AccountMoveService {
this.copyBlocking(src, dst),
this.copyMutings(src, dst),
this.updateLists(src, dst),
this.mergeModerationNote(src, dst),
this.insertAccountMoveLog(src, dst),
]);
} catch {
/* 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
private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: MiUser): Promise<void> {
if (localFollowerIds.length === 0) return;

View file

@ -101,6 +101,7 @@ import { HashtagEntityService } from './entities/HashtagEntityService.js';
import { InstanceEntityService } from './entities/InstanceEntityService.js';
import { InviteCodeEntityService } from './entities/InviteCodeEntityService.js';
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
import { UserAccountMoveLogEntityService } from './entities/UserAccountMoveLogEntityService.js';
import { MutingEntityService } from './entities/MutingEntityService.js';
import { RenoteMutingEntityService } from './entities/RenoteMutingEntityService.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 $InviteCodeEntityService: Provider = { provide: 'InviteCodeEntityService', useExisting: InviteCodeEntityService };
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
const $UserAccountMoveLogEntityService: Provider = { provide: 'UserAccountMoveLogEntityService', useExisting: UserAccountMoveLogEntityService };
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
const $RenoteMutingEntityService: Provider = { provide: 'RenoteMutingEntityService', useExisting: RenoteMutingEntityService };
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
@ -382,6 +384,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService,
InviteCodeEntityService,
ModerationLogEntityService,
UserAccountMoveLogEntityService,
MutingEntityService,
RenoteMutingEntityService,
NoteEntityService,
@ -518,6 +521,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService,
$InviteCodeEntityService,
$ModerationLogEntityService,
$UserAccountMoveLogEntityService,
$MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService,
@ -654,6 +658,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService,
InviteCodeEntityService,
ModerationLogEntityService,
UserAccountMoveLogEntityService,
MutingEntityService,
RenoteMutingEntityService,
NoteEntityService,
@ -789,6 +794,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService,
$InviteCodeEntityService,
$ModerationLogEntityService,
$UserAccountMoveLogEntityService,
$MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService,

View file

@ -682,10 +682,7 @@ export class ApPersonService implements OnModuleInit {
// まずサーバー内で検索して様子見
let dst = await this.fetchPerson(src.movedToUri);
if (dst && this.userEntityService.isLocalUser(dst)) {
// targetがローカルユーザーだった場合データベースから引っ張ってくる
dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as MiLocalUser;
} else if (dst) {
if (dst) {
if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move';
// targetを見つけたことがあるならtargetをupdatePersonする
@ -702,13 +699,15 @@ export class ApPersonService implements OnModuleInit {
dst = await this.resolvePerson(src.movedToUri);
}
if (dst.movedToUri === dst.uri) return 'skip: movedTo itself (dst)'; //
if (src.movedToUri !== dst.uri) return 'skip: missmatch uri'; //
if (dst.movedToUri === src.uri) return 'skip: dst.movedToUri === src.uri';
const dstUri = this.userEntityService.getUserUri(dst);
const srcUri = this.userEntityService.getUserUri(src);
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) {
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';
}

View file

@ -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);
}
}