fix(migration): permissions issue (#6177)

* fix(migration): permissions issue

* feat: add new migration
This commit is contained in:
Zeke Zhang 2025-02-07 19:43:29 +08:00 committed by GitHub
parent 1c1b9a3fe6
commit 17541f94d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 204 additions and 12 deletions

View File

@ -0,0 +1,111 @@
import { MockServer, createMockServer } from '@nocobase/test';
import Migration, { getIds, schemaToRoutes } from '../migrations/2024122912211-transform-menu-schema-to-routes';
describe('transform-menu-schema-to-routes', () => {
let app: MockServer;
beforeEach(async () => {
app = await createMockServer({
plugins: ['nocobase'],
});
await app.version.update('1.5.0');
});
afterEach(async () => {
await app.destroy();
});
describe('migration', () => {
test('should skip if desktop routes already exist', async () => {
const desktopRoutes = app.db.getRepository('desktopRoutes');
await desktopRoutes.create({
type: 'page',
title: 'Test Page',
} as any);
const migration = new Migration({
db: app.db,
app: app,
} as any);
await migration.up();
const count = await desktopRoutes.count();
expect(count).toBe(1);
});
});
describe('schemaToRoutes', () => {
test('should transform group menu item', async () => {
const schema = {
properties: {
group1: {
'x-component': 'Menu.SubMenu',
title: 'Group 1',
'x-uid': 'group-1',
'x-component-props': {
icon: 'GroupIcon',
},
properties: {},
},
},
};
const routes = await schemaToRoutes(schema, app.db.getRepository('uiSchemas'));
expect(routes[0]).toMatchObject({
type: 'group',
title: 'Group 1',
icon: 'GroupIcon',
schemaUid: 'group-1',
hideInMenu: false,
});
});
test('should transform link menu item', async () => {
const schema = {
properties: {
link1: {
'x-component': 'Menu.URL',
title: 'Link 1',
'x-uid': 'link-1',
'x-component-props': {
icon: 'LinkIcon',
href: 'https://example.com',
params: { foo: 'bar' },
},
},
},
};
const routes = await schemaToRoutes(schema, app.db.getRepository('uiSchemas'));
expect(routes[0]).toMatchObject({
type: 'link',
title: 'Link 1',
icon: 'LinkIcon',
options: {
href: 'https://example.com',
params: { foo: 'bar' },
},
schemaUid: 'link-1',
});
});
});
describe('getIds', () => {
test('should correctly identify ids to add and remove', () => {
const desktopRoutes = [
{ id: 1, type: 'page', menuSchemaUid: 'page-1' },
{ id: 2, type: 'page', menuSchemaUid: 'page-2' },
{ id: 3, type: 'tabs', parentId: 1 },
];
const menuUiSchemas = [
{ 'x-uid': 'page-1' },
];
const { needRemoveIds, needAddIds } = getIds(desktopRoutes, menuUiSchemas);
expect(needRemoveIds).toContain(2);
expect(needAddIds).toContain(1);
expect(needAddIds).toContain(3);
});
});
});

View File

@ -35,24 +35,30 @@ export default class extends Migration {
// 2. 将旧版的权限配置,转换为新版的权限配置
const roles = await rolesRepository.find({
appends: ['desktopRoutes', 'menuUiSchemas'],
appends: ['menuUiSchemas'],
transaction,
});
const allDesktopRoutes = await desktopRoutes.find({ transaction });
for (const role of roles) {
const menuUiSchemas = role.menuUiSchemas || [];
const desktopRoutes = role.desktopRoutes || [];
const needRemoveIds = getNeedRemoveIds(desktopRoutes, menuUiSchemas);
const { needRemoveIds, needAddIds } = getIds(allDesktopRoutes, menuUiSchemas);
if (needRemoveIds.length === 0) {
continue;
if (needRemoveIds.length > 0) {
// @ts-ignore
await this.db.getRepository('roles.desktopRoutes', role.name).remove({
tk: needRemoveIds,
transaction,
});
}
// @ts-ignore
await this.db.getRepository('roles.desktopRoutes', role.name).remove({
tk: needRemoveIds,
transaction,
});
if (needAddIds.length > 0) {
// @ts-ignore
await this.db.getRepository('roles.desktopRoutes', role.name).add({
tk: needAddIds,
transaction,
});
}
}
}
@ -170,9 +176,9 @@ export async function schemaToRoutes(schema: any, uiSchemas: any) {
return Promise.all(result);
}
function getNeedRemoveIds(desktopRoutes: any[], menuUiSchemas: any[]) {
export function getIds(desktopRoutes: any[], menuUiSchemas: any[]) {
const uidList = menuUiSchemas.map((item) => item['x-uid']);
return desktopRoutes
const needRemoveIds = desktopRoutes
.filter((item) => {
// 之前是不支持配置 tab 的权限的,所以所有的 tab 都不会存在于旧版的 menuUiSchemas 中
if (item.type === 'tabs') {
@ -189,4 +195,7 @@ function getNeedRemoveIds(desktopRoutes: any[], menuUiSchemas: any[]) {
return !uidList.includes(item?.schemaUid);
})
.map((item) => item?.id);
const needAddIds = desktopRoutes.map((item) => item?.id).filter((id) => !needRemoveIds.includes(id));
return { needRemoveIds, needAddIds };
}

View File

@ -0,0 +1,72 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Migration } from '@nocobase/server';
export default class extends Migration {
appVersion = '<1.6.0';
async up() {
const desktopRoutes = this.db.getRepository('desktopRoutes');
const count = await desktopRoutes.count();
if (!count) {
return;
}
const rolesRepository = this.db.getRepository('roles');
try {
await this.db.sequelize.transaction(async (transaction) => {
const roles = await rolesRepository.find({
appends: ['menuUiSchemas'],
transaction,
});
const allDesktopRoutes = await desktopRoutes.find({ transaction });
for (const role of roles) {
const menuUiSchemas = role.menuUiSchemas || [];
const { needAddIds } = getIds(allDesktopRoutes, menuUiSchemas);
if (needAddIds.length > 0) {
// @ts-ignore
await this.db.getRepository('roles.desktopRoutes', role.name).add({
tk: needAddIds,
transaction,
});
}
}
});
} catch (error) {
console.error('Migration failed:', error);
throw error;
}
}
}
export function getIds(desktopRoutes: any[], menuUiSchemas: any[]) {
const uidList = menuUiSchemas.map((item) => item['x-uid']);
const needRemoveIds = desktopRoutes
.filter((item) => {
// 之前是不支持配置 tab 的权限的,所以所有的 tab 都不会存在于旧版的 menuUiSchemas 中
if (item.type === 'tabs') {
// tab 的父节点就是一个 page
const page = desktopRoutes.find((route) => route?.id === item?.parentId);
// tab 要不要过滤掉和它的父节点page有关
return !uidList.includes(page?.menuSchemaUid);
}
if (item.type === 'page') {
return !uidList.includes(item?.menuSchemaUid);
}
return !uidList.includes(item?.schemaUid);
})
.map((item) => item?.id);
const needAddIds = desktopRoutes.map((item) => item?.id).filter((id) => !needRemoveIds.includes(id));
return { needRemoveIds, needAddIds };
}