diff --git a/packages/plugins/@nocobase/plugin-client/src/server/__tests__/2024122912211-transform-menu-schema-to-routes.test.ts b/packages/plugins/@nocobase/plugin-client/src/server/__tests__/2024122912211-transform-menu-schema-to-routes.test.ts new file mode 100644 index 0000000000..c4484349c3 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-client/src/server/__tests__/2024122912211-transform-menu-schema-to-routes.test.ts @@ -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); + }); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-client/src/server/migrations/2024122912211-transform-menu-schema-to-routes.ts b/packages/plugins/@nocobase/plugin-client/src/server/migrations/2024122912211-transform-menu-schema-to-routes.ts index d0383a6c30..743d0d96e7 100644 --- a/packages/plugins/@nocobase/plugin-client/src/server/migrations/2024122912211-transform-menu-schema-to-routes.ts +++ b/packages/plugins/@nocobase/plugin-client/src/server/migrations/2024122912211-transform-menu-schema-to-routes.ts @@ -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 }; } diff --git a/packages/plugins/@nocobase/plugin-client/src/server/migrations/202502071837-fix-permissions.ts b/packages/plugins/@nocobase/plugin-client/src/server/migrations/202502071837-fix-permissions.ts new file mode 100644 index 0000000000..1b047d63dc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-client/src/server/migrations/202502071837-fix-permissions.ts @@ -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 }; +}