Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance: アカウント移行機能を使用したユーザーに対してのモデレーションの強化 #719

Merged
merged 10 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -4368,6 +4384,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 @@ -1088,6 +1092,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
Loading