Copy role on move (#15745)
* feat(backend): copyOnMoveAccount * feat(endpoints): copyOnMoveAccount * feat(frontend): copyOnMoveAccount * docs(changelog): アカウントのマイグレーション時に古いアカウントからロールをコピーできるようになりました。 * fix: spdx header for migration * Update locales/ja-JP.yml * copyOnMoveAccount -> preserveAssignmentOnMoveAccount * fix: check for preserveAssignmentOnMoveAccount --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
440a4a4d8b
commit
cab82452ec
|
@ -11,6 +11,8 @@
|
||||||
- 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です
|
- 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です
|
||||||
- 参加中のルームをミュートして通知が来ないように設定可能です
|
- 参加中のルームをミュートして通知が来ないように設定可能です
|
||||||
- メッセージにはリアクションも可能です
|
- メッセージにはリアクションも可能です
|
||||||
|
- Feat: アカウントのマイグレーション時に古いアカウントからロールをコピーできるようになりました。
|
||||||
|
- 管理者がロールの設定でマイグレーション時にコピーするかを指定できるようになります。
|
||||||
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||||
- Misskeyネイティブでダッシュボードを実装予定です
|
- Misskeyネイティブでダッシュボードを実装予定です
|
||||||
- Enhance: フロントエンドのエラートラッキングができるように
|
- Enhance: フロントエンドのエラートラッキングができるように
|
||||||
|
|
|
@ -7361,6 +7361,14 @@ export interface Locale extends ILocale {
|
||||||
* 数値が大きいほどUI上で先頭に表示されます。
|
* 数値が大きいほどUI上で先頭に表示されます。
|
||||||
*/
|
*/
|
||||||
"descriptionOfDisplayOrder": string;
|
"descriptionOfDisplayOrder": string;
|
||||||
|
/**
|
||||||
|
* アサイン状態を移行先アカウントにも引き継ぐ
|
||||||
|
*/
|
||||||
|
"preserveAssignmentOnMoveAccount": string;
|
||||||
|
/**
|
||||||
|
* オンにすると、このロールが付与されたアカウントが移行された際に、移行先アカウントにもこのロールが引き継がれるようになります。
|
||||||
|
*/
|
||||||
|
"preserveAssignmentOnMoveAccount_description": string;
|
||||||
/**
|
/**
|
||||||
* モデレーターのメンバー編集を許可
|
* モデレーターのメンバー編集を許可
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1907,6 +1907,8 @@ _role:
|
||||||
descriptionOfIsExplorable: "オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。"
|
descriptionOfIsExplorable: "オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。"
|
||||||
displayOrder: "表示順"
|
displayOrder: "表示順"
|
||||||
descriptionOfDisplayOrder: "数値が大きいほどUI上で先頭に表示されます。"
|
descriptionOfDisplayOrder: "数値が大きいほどUI上で先頭に表示されます。"
|
||||||
|
preserveAssignmentOnMoveAccount: "アサイン状態を移行先アカウントにも引き継ぐ"
|
||||||
|
preserveAssignmentOnMoveAccount_description: "オンにすると、このロールが付与されたアカウントが移行された際に、移行先アカウントにもこのロールが引き継がれるようになります。"
|
||||||
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
|
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
|
||||||
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
|
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
|
||||||
priority: "優先度"
|
priority: "優先度"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class RoleCopyOnMoveAccount1743558299182 {
|
||||||
|
name = 'RoleCopyOnMoveAccount1743558299182'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "role" ADD "preserveAssignmentOnMoveAccount" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "preserveAssignmentOnMoveAccount"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
|
@ -61,6 +62,7 @@ export class AccountMoveService {
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
private systemAccountService: SystemAccountService,
|
private systemAccountService: SystemAccountService,
|
||||||
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +121,7 @@ export class AccountMoveService {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.copyBlocking(src, dst),
|
this.copyBlocking(src, dst),
|
||||||
this.copyMutings(src, dst),
|
this.copyMutings(src, dst),
|
||||||
|
this.copyRoles(src, dst),
|
||||||
this.updateLists(src, dst),
|
this.updateLists(src, dst),
|
||||||
]);
|
]);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -201,6 +204,32 @@ export class AccountMoveService {
|
||||||
await this.mutingsRepository.insert(arrayToInsert);
|
await this.mutingsRepository.insert(arrayToInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async copyRoles(src: ThinUser, dst: ThinUser): Promise<void> {
|
||||||
|
// Insert new roles with the same values except userId
|
||||||
|
// role service may have cache for roles so retrieve roles from service
|
||||||
|
const [oldRoleAssignments, roles] = await Promise.all([
|
||||||
|
this.roleService.getUserAssigns(src.id),
|
||||||
|
this.roleService.getRoles(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (oldRoleAssignments.length === 0) return;
|
||||||
|
|
||||||
|
// No promise all since the only async operation is writing to the database
|
||||||
|
for (const oldRoleAssignment of oldRoleAssignments) {
|
||||||
|
const role = roles.find(x => x.id === oldRoleAssignment.roleId);
|
||||||
|
if (role == null) continue; // Very unlikely however removing role may cause this case
|
||||||
|
if (!role.preserveAssignmentOnMoveAccount) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.roleService.assign(dst.id, role.id, oldRoleAssignment.expiresAt);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RoleService.AlreadyAssignedError) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update lists while moving accounts.
|
* Update lists while moving accounts.
|
||||||
* - No removal of the old account from the lists
|
* - No removal of the old account from the lists
|
||||||
|
|
|
@ -630,6 +630,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
isModerator: values.isModerator,
|
isModerator: values.isModerator,
|
||||||
isExplorable: values.isExplorable,
|
isExplorable: values.isExplorable,
|
||||||
asBadge: values.asBadge,
|
asBadge: values.asBadge,
|
||||||
|
preserveAssignmentOnMoveAccount: values.preserveAssignmentOnMoveAccount,
|
||||||
canEditMembersByModerator: values.canEditMembersByModerator,
|
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||||
displayOrder: values.displayOrder,
|
displayOrder: values.displayOrder,
|
||||||
policies: values.policies,
|
policies: values.policies,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type { MiRole } from '@/models/Role.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleEntityService {
|
export class RoleEntityService {
|
||||||
|
@ -31,7 +32,7 @@ export class RoleEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiRole['id'] | MiRole,
|
src: MiRole['id'] | MiRole,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
): Promise<Packed<'Role'>> {
|
||||||
const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
|
const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
const assignedCount = await this.roleAssignmentsRepository.createQueryBuilder('assign')
|
const assignedCount = await this.roleAssignmentsRepository.createQueryBuilder('assign')
|
||||||
|
@ -67,6 +68,7 @@ export class RoleEntityService {
|
||||||
isModerator: role.isModerator,
|
isModerator: role.isModerator,
|
||||||
isExplorable: role.isExplorable,
|
isExplorable: role.isExplorable,
|
||||||
asBadge: role.asBadge,
|
asBadge: role.asBadge,
|
||||||
|
preserveAssignmentOnMoveAccount: role.preserveAssignmentOnMoveAccount,
|
||||||
canEditMembersByModerator: role.canEditMembersByModerator,
|
canEditMembersByModerator: role.canEditMembersByModerator,
|
||||||
displayOrder: role.displayOrder,
|
displayOrder: role.displayOrder,
|
||||||
policies: policies,
|
policies: policies,
|
||||||
|
|
|
@ -248,6 +248,11 @@ export class MiRole {
|
||||||
})
|
})
|
||||||
public isExplorable: boolean;
|
public isExplorable: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public preserveAssignmentOnMoveAccount: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -389,6 +389,11 @@ export const packedRoleSchema = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
example: false,
|
example: false,
|
||||||
},
|
},
|
||||||
|
preserveAssignmentOnMoveAccount: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
example: false,
|
||||||
|
},
|
||||||
canEditMembersByModerator: {
|
canEditMembersByModerator: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -36,6 +36,7 @@ export const paramDef = {
|
||||||
isAdministrator: { type: 'boolean' },
|
isAdministrator: { type: 'boolean' },
|
||||||
isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility
|
isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility
|
||||||
asBadge: { type: 'boolean' },
|
asBadge: { type: 'boolean' },
|
||||||
|
preserveAssignmentOnMoveAccount: { type: 'boolean' },
|
||||||
canEditMembersByModerator: { type: 'boolean' },
|
canEditMembersByModerator: { type: 'boolean' },
|
||||||
displayOrder: { type: 'number' },
|
displayOrder: { type: 'number' },
|
||||||
policies: {
|
policies: {
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const paramDef = {
|
||||||
isAdministrator: { type: 'boolean' },
|
isAdministrator: { type: 'boolean' },
|
||||||
isExplorable: { type: 'boolean' },
|
isExplorable: { type: 'boolean' },
|
||||||
asBadge: { type: 'boolean' },
|
asBadge: { type: 'boolean' },
|
||||||
|
preserveAssignmentOnMoveAccount: { type: 'boolean' },
|
||||||
canEditMembersByModerator: { type: 'boolean' },
|
canEditMembersByModerator: { type: 'boolean' },
|
||||||
displayOrder: { type: 'number' },
|
displayOrder: { type: 'number' },
|
||||||
policies: {
|
policies: {
|
||||||
|
@ -78,6 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
isAdministrator: ps.isAdministrator,
|
isAdministrator: ps.isAdministrator,
|
||||||
isExplorable: ps.isExplorable,
|
isExplorable: ps.isExplorable,
|
||||||
asBadge: ps.asBadge,
|
asBadge: ps.asBadge,
|
||||||
|
preserveAssignmentOnMoveAccount: ps.preserveAssignmentOnMoveAccount,
|
||||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||||
displayOrder: ps.displayOrder,
|
displayOrder: ps.displayOrder,
|
||||||
policies: ps.policies,
|
policies: ps.policies,
|
||||||
|
|
|
@ -52,6 +52,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkSwitch v-model="role.preserveAssignmentOnMoveAccount" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.preserveAssignmentOnMoveAccount }}</template>
|
||||||
|
<template #caption>{{ i18n.ts._role.preserveAssignmentOnMoveAccount_description }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSwitch v-model="role.canEditMembersByModerator" :readonly="readonly">
|
<MkSwitch v-model="role.canEditMembersByModerator" :readonly="readonly">
|
||||||
<template #label>{{ i18n.ts._role.canEditMembersByModerator }}</template>
|
<template #label>{{ i18n.ts._role.canEditMembersByModerator }}</template>
|
||||||
<template #caption>{{ i18n.ts._role.descriptionOfCanEditMembersByModerator }}</template>
|
<template #caption>{{ i18n.ts._role.descriptionOfCanEditMembersByModerator }}</template>
|
||||||
|
|
|
@ -5135,6 +5135,8 @@ export type components = {
|
||||||
/** @example false */
|
/** @example false */
|
||||||
asBadge: boolean;
|
asBadge: boolean;
|
||||||
/** @example false */
|
/** @example false */
|
||||||
|
preserveAssignmentOnMoveAccount: boolean;
|
||||||
|
/** @example false */
|
||||||
canEditMembersByModerator: boolean;
|
canEditMembersByModerator: boolean;
|
||||||
policies: {
|
policies: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
|
@ -9392,6 +9394,7 @@ export type operations = {
|
||||||
/** @default false */
|
/** @default false */
|
||||||
isExplorable?: boolean;
|
isExplorable?: boolean;
|
||||||
asBadge: boolean;
|
asBadge: boolean;
|
||||||
|
preserveAssignmentOnMoveAccount?: boolean;
|
||||||
canEditMembersByModerator: boolean;
|
canEditMembersByModerator: boolean;
|
||||||
displayOrder: number;
|
displayOrder: number;
|
||||||
policies: Record<string, never>;
|
policies: Record<string, never>;
|
||||||
|
@ -9667,6 +9670,7 @@ export type operations = {
|
||||||
isAdministrator?: boolean;
|
isAdministrator?: boolean;
|
||||||
isExplorable?: boolean;
|
isExplorable?: boolean;
|
||||||
asBadge?: boolean;
|
asBadge?: boolean;
|
||||||
|
preserveAssignmentOnMoveAccount?: boolean;
|
||||||
canEditMembersByModerator?: boolean;
|
canEditMembersByModerator?: boolean;
|
||||||
displayOrder?: number;
|
displayOrder?: number;
|
||||||
policies?: Record<string, never>;
|
policies?: Record<string, never>;
|
||||||
|
|
Loading…
Reference in New Issue