Skip to content

Commit

Permalink
Enhance: アカウント移行機能を使用したユーザーに対してのモデレーションの強化 (#719)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent 2fe5bb0 commit 6c732d1
Show file tree
Hide file tree
Showing 29 changed files with 593 additions and 13 deletions.
20 changes: 20 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,22 @@ export interface Locale extends ILocale {
* モデログ
*/
"moderationLogs": string;
/**
* アカウント移行使用ログ
*/
"userAccountMoveLogs": string;
/**
* {from} が {to} にアカウントを移行しました
*/
"userAccountMoveLogsTitle": ParameterizedString<"from" | "to">;
/**
* 移行先のアカウントのID
*/
"movedToId": string;
/**
* 移行元のアカウントのID
*/
"moveFromId": string;
/**
* {n}人が投稿
*/
Expand Down Expand Up @@ -4375,6 +4391,10 @@ export interface Locale extends ILocale {
* このユーザーは新しいアカウントに移行しました:
*/
"accountMoved": string;
/**
* このユーザーは次のアカウントから移行されました:
*/
"accountMovedFrom": string;
/**
* このアカウントは移行されています
*/
Expand Down
5 changes: 5 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ moderation: "モデレーション"
moderationNote: "モデレーションノート"
addModerationNote: "モデレーションノートを追加する"
moderationLogs: "モデログ"
userAccountMoveLogs: "アカウント移行使用ログ"
userAccountMoveLogsTitle: "{from} が {to} にアカウントを移行しました"
movedToId: "移行先のアカウントのID"
moveFromId: "移行元のアカウントのID"
nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー"
Expand Down Expand Up @@ -1089,6 +1093,7 @@ audioFiles: "音声"
dataSaver: "データセーバー"
accountMigration: "アカウントの移行"
accountMoved: "このユーザーは新しいアカウントに移行しました:"
accountMovedFrom: "このユーザーは次のアカウントから移行されました:"
accountMovedShort: "このアカウントは移行されています"
operationForbidden: "この操作はできません"
forceShowAds: "常に広告を表示する"
Expand Down
19 changes: 19 additions & 0 deletions packages/backend/migration/1724749627479-useraccountmovelogs.js
Original file line number Diff line number Diff line change
@@ -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"`);
}
}
39 changes: 38 additions & 1 deletion packages/backend/src/core/AccountMoveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/core/CoreModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -382,6 +384,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService,
InviteCodeEntityService,
ModerationLogEntityService,
UserAccountMoveLogEntityService,
MutingEntityService,
RenoteMutingEntityService,
NoteEntityService,
Expand Down Expand Up @@ -518,6 +521,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService,
$InviteCodeEntityService,
$ModerationLogEntityService,
$UserAccountMoveLogEntityService,
$MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService,
Expand Down Expand Up @@ -654,6 +658,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
InstanceEntityService,
InviteCodeEntityService,
ModerationLogEntityService,
UserAccountMoveLogEntityService,
MutingEntityService,
RenoteMutingEntityService,
NoteEntityService,
Expand Down Expand Up @@ -789,6 +794,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$InstanceEntityService,
$InviteCodeEntityService,
$ModerationLogEntityService,
$UserAccountMoveLogEntityService,
$MutingEntityService,
$RenoteMutingEntityService,
$NoteEntityService,
Expand Down
15 changes: 7 additions & 8 deletions packages/backend/src/core/activitypub/models/ApPersonService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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する
Expand All @@ -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';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

1 change: 1 addition & 0 deletions packages/backend/src/di-symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const DI = {
userListMembershipsRepository: Symbol('userListMembershipsRepository'),
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
userIpsRepository: Symbol('userIpsRepository'),
userAccountMoveLogRepository: Symbol('userAccountMoveLogRepository'),
usedUsernamesRepository: Symbol('usedUsernamesRepository'),
followingsRepository: Symbol('followingsRepository'),
followRequestsRepository: Symbol('followRequestsRepository'),
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/misc/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.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 { packedSigninSchema } from '@/models/json-schema/signin.js';
import {
Expand Down Expand Up @@ -71,6 +72,7 @@ export const refs = {

UserList: packedUserListSchema,
UserListMembership: packedUserListMembershipSchema,
UserAccountMoveLog: packedUserAccountMoveLogSchema,
Ad: packedAdSchema,
Announcement: packedAnnouncementSchema,
App: packedAppSchema,
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/models/RepositoryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
MiUserProfile,
MiUserPublickey,
MiUserSecurityKey,
MiUserAccountMoveLog,
MiWebhook,
MiBubbleGameRecord,
MiReversiGame,
Expand Down Expand Up @@ -200,6 +201,12 @@ const $userListMembershipsRepository: Provider = {
inject: [DI.db],
};

const $userAccountMoveLogRepository: Provider = {
provide: DI.userAccountMoveLogRepository,
useFactory: (db: DataSource) => db.getRepository(MiUserAccountMoveLog),
inject: [DI.db],
};

const $userNotePiningsRepository: Provider = {
provide: DI.userNotePiningsRepository,
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining),
Expand Down Expand Up @@ -524,6 +531,7 @@ const $abuseReportResolversRepository: Provider = {
$userListsRepository,
$userListFavoritesRepository,
$userListMembershipsRepository,
$userAccountMoveLogRepository,
$userNotePiningsRepository,
$userIpsRepository,
$usedUsernamesRepository,
Expand Down Expand Up @@ -596,6 +604,7 @@ const $abuseReportResolversRepository: Provider = {
$userListsRepository,
$userListFavoritesRepository,
$userListMembershipsRepository,
$userAccountMoveLogRepository,
$userNotePiningsRepository,
$userIpsRepository,
$usedUsernamesRepository,
Expand Down
35 changes: 35 additions & 0 deletions packages/backend/src/models/UserAccountMoveLog.ts
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 3 additions & 0 deletions packages/backend/src/models/_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { MiUserProfile } from '@/models/UserProfile.js';
import { MiUserPublickey } from '@/models/UserPublickey.js';
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiUserAccountMoveLog } from '@/models/UserAccountMoveLog.js';
import { MiWebhook } from '@/models/Webhook.js';
import { MiChannel } from '@/models/Channel.js';
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
Expand Down Expand Up @@ -146,6 +147,7 @@ export {
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
MiUserAccountMoveLog,
};

export type AbuseReportResolversRepository = Repository<MiAbuseReportResolver>;
Expand Down Expand Up @@ -208,6 +210,7 @@ export type UserPendingsRepository = Repository<MiUserPending>;
export type UserProfilesRepository = Repository<MiUserProfile>;
export type UserPublickeysRepository = Repository<MiUserPublickey>;
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey>;
export type UserAccountMoveLogRepository = Repository<MiUserAccountMoveLog>;
export type WebhooksRepository = Repository<MiWebhook>;
export type ChannelsRepository = Repository<MiChannel>;
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation>;
Expand Down
Loading

0 comments on commit 6c732d1

Please sign in to comment.