mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
fix: the page is blank after switching roles (#6388)
* fix: set mobileRoutes do not take effect v2 * chore: remove test only * feat: roles desktop routes migration script and test * feat: roles mobile routes migration script and test --------- Co-authored-by: 霍世杰 <huoshijie@192.168.1.70>
This commit is contained in:
parent
1c8c796938
commit
dad8213cad
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { MockServer, createMockServer } from '@nocobase/test';
|
||||
import Migration from '../migrations/202503091240-fix-roles-desktop-routes';
|
||||
import { vi, describe, beforeEach, afterEach, test, expect } from 'vitest';
|
||||
import { Model, Repository } from '@nocobase/database';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('202503091240-fix-roles-desktop-routes', () => {
|
||||
let app: MockServer, role1: Model, role2: Model, desktopRoutesRepo: Repository, rolesDesktopRoutesRepo: Repository;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['nocobase'],
|
||||
});
|
||||
await app.version.update('1.5.0');
|
||||
role1 = await app.db.getRepository('roles').create({
|
||||
values: {
|
||||
name: 'role1',
|
||||
},
|
||||
});
|
||||
role2 = await app.db.getRepository('roles').create({
|
||||
values: {
|
||||
name: 'role2',
|
||||
},
|
||||
});
|
||||
desktopRoutesRepo = app.db.getRepository('desktopRoutes');
|
||||
rolesDesktopRoutesRepo = app.db.getRepository('rolesDesktopRoutes');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
async function createTestData() {
|
||||
const idSet = new Set();
|
||||
while (idSet.size < 8) {
|
||||
idSet.add(Math.ceil(10 + Math.random() * 10));
|
||||
}
|
||||
const ids = Array.from(idSet);
|
||||
const records = [
|
||||
{ id: ids[0], title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[1], title: 'tabs1', type: 'tabs', parentId: ids[0], hideInMenu: null, hidden: true },
|
||||
{ id: ids[2], title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[3], title: 'tabs3', type: 'tabs', parentId: ids[2], hideInMenu: null, hidden: true },
|
||||
{ id: ids[4], title: 'page4', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[5], title: 'tabs4', type: 'tabs', parentId: ids[4], hideInMenu: null, hidden: null },
|
||||
{ id: ids[6], title: 'tabs5', type: 'tabs', parentId: ids[4], hideInMenu: null, hidden: null },
|
||||
];
|
||||
return await desktopRoutesRepo.createMany({ records });
|
||||
}
|
||||
it('should create miss child routes', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesDesktopRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), desktopRouteId: models[0].id },
|
||||
{ roleName: role2.get('name'), desktopRouteId: models[2].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesDesktopRoutes.length).toBe(4);
|
||||
const role1Routes = await rolesDesktopRoutes.filter((x) => x.roleName === role1.get('name'));
|
||||
expect(role1Routes.map((x) => x.get('desktopRouteId'))).toContain(models[0].id);
|
||||
expect(role1Routes.map((x) => x.get('desktopRouteId'))).toContain(models[1].id);
|
||||
const role2Routes = await rolesDesktopRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).toContain(models[2].id);
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).toContain(models[3].id);
|
||||
});
|
||||
|
||||
it('should create miss child routes when has repeat data', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesDesktopRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), desktopRouteId: models[0].id },
|
||||
{ roleName: role1.get('name'), desktopRouteId: models[1].id },
|
||||
{ roleName: role2.get('name'), desktopRouteId: models[2].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesDesktopRoutes.length).toBe(4);
|
||||
const role1Routes = await rolesDesktopRoutes.filter((x) => x.roleName === role1.get('name'));
|
||||
expect(role1Routes.map((x) => x.get('desktopRouteId'))).toContain(models[0].id);
|
||||
expect(role1Routes.map((x) => x.get('desktopRouteId'))).toContain(models[1].id);
|
||||
const role2Routes = await rolesDesktopRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).toContain(models[2].id);
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).toContain(models[3].id);
|
||||
});
|
||||
|
||||
it('should create miss child routes no includes hidden = false', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesDesktopRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), desktopRouteId: models[0].id },
|
||||
{ roleName: role1.get('name'), desktopRouteId: models[2].id },
|
||||
{ roleName: role2.get('name'), desktopRouteId: models[4].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesDesktopRoutes.map((x) => x.get('desktopRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([models[1].id, models[3].id]),
|
||||
);
|
||||
const role2Routes = await rolesDesktopRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).not.toContain(models[5].id);
|
||||
expect(role2Routes.map((x) => x.get('desktopRouteId'))).not.toContain(models[6].id);
|
||||
});
|
||||
});
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import Database from '@nocobase/database';
|
||||
import Database, { Model, Repository } from '@nocobase/database';
|
||||
import { createMockServer, MockServer } from '@nocobase/test';
|
||||
|
||||
describe('desktopRoutes:listAccessible', () => {
|
||||
@ -168,3 +168,114 @@ describe('desktopRoutes:listAccessible', () => {
|
||||
expect(response.body.data[0].children.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('desktopRoutes', async () => {
|
||||
let app: MockServer, db: Database, desktopRoutesRepo: Repository, rolesDesktopRoutesRepo: Repository;
|
||||
let role: Model;
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: [
|
||||
'error-handler',
|
||||
'client',
|
||||
'field-sort',
|
||||
'acl',
|
||||
'ui-schema-storage',
|
||||
'system-settings',
|
||||
'data-source-main',
|
||||
'data-source-manager',
|
||||
],
|
||||
});
|
||||
db = app.db;
|
||||
desktopRoutesRepo = db.getRepository('desktopRoutes');
|
||||
rolesDesktopRoutesRepo = db.getRepository('rolesDesktopRoutes');
|
||||
role = await db.getRepository('roles').create({ values: { name: 'role1' } });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it(`should rolesDesktopRoutes includes tabs when create menu`, async () => {
|
||||
const records = [
|
||||
{ id: 50, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 51, title: 'tabs1', type: 'tabs', parentId: 50, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await desktopRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesDesktopRoutes').model.bulkCreate([{ roleName: role.get('name'), desktopRouteId: 50 }]);
|
||||
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesDesktopRoutes.length).toBe(2);
|
||||
expect(rolesDesktopRoutes.map((x) => x.get('desktopRouteId'))).toStrictEqual(expect.arrayContaining([50, 51]));
|
||||
});
|
||||
|
||||
it(`should rolesDesktopRoutes includes tabs when bulk create menu`, async () => {
|
||||
const records = [
|
||||
{ id: 52, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 53, title: 'tabs1', type: 'tabs', parentId: 52, hideInMenu: null, hidden: true },
|
||||
{ id: 54, title: 'page2', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 55, title: 'tabs2', type: 'tabs', parentId: 54, hideInMenu: null, hidden: true },
|
||||
{ id: 56, title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 57, title: 'tabs3', type: 'tabs', parentId: 56, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await desktopRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesDesktopRoutes').model.bulkCreate([
|
||||
{ roleName: role.get('name'), desktopRouteId: 52 },
|
||||
{ roleName: role.get('name'), desktopRouteId: 54 },
|
||||
]);
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesDesktopRoutes.length).toStrictEqual(4);
|
||||
expect(rolesDesktopRoutes.map((x) => x.get('desktopRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([52, 53, 54, 55]),
|
||||
);
|
||||
});
|
||||
|
||||
it(`should rolesDesktopRoutes includes tabs when create menu and invalid data`, async () => {
|
||||
const records = [
|
||||
{ id: 60, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 61, title: 'tabs1', type: 'tabs', parentId: 60, hideInMenu: null, hidden: true },
|
||||
{ id: 62, title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 63, title: 'tabs3', type: 'tabs', parentId: 62, hideInMenu: null, hidden: null },
|
||||
{ id: 64, title: 'tabs4', type: 'tabs', parentId: 62, hideInMenu: null, hidden: null },
|
||||
];
|
||||
await desktopRoutesRepo.createMany({ records });
|
||||
await rolesDesktopRoutesRepo.create({ values: { roleName: role.get('name'), desktopRouteId: 61 } });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesDesktopRoutes').model.bulkCreate([
|
||||
{ roleName: role.get('name'), desktopRouteId: 60 },
|
||||
{ roleName: role.get('name'), desktopRouteId: 62 },
|
||||
{ roleName: role.get('name'), desktopRouteId: 63 },
|
||||
]);
|
||||
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesDesktopRoutes.length).toBe(4);
|
||||
expect(rolesDesktopRoutes.map((x) => x.get('desktopRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([60, 61, 62, 63]),
|
||||
);
|
||||
});
|
||||
|
||||
it(`should rolesDesktopRoutes destroy tabs when remove menu and invalid data`, async () => {
|
||||
const records = [
|
||||
{ id: 70, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 71, title: 'tabs1', type: 'tabs', parentId: 70, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await desktopRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesDesktopRoutes').model.bulkCreate([{ roleName: role.get('name'), desktopRouteId: 70 }]);
|
||||
|
||||
let rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesDesktopRoutes.length).toBe(2);
|
||||
expect(rolesDesktopRoutes.map((x) => x.get('desktopRouteId'))).toStrictEqual(expect.arrayContaining([70, 71]));
|
||||
|
||||
const transaction = await db.sequelize.transaction();
|
||||
await db.getCollection('rolesDesktopRoutes').model.destroy({
|
||||
where: { roleName: role.get('name'), desktopRouteId: [70] },
|
||||
individualHooks: false,
|
||||
transaction,
|
||||
});
|
||||
await transaction.commit();
|
||||
rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesDesktopRoutes.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class extends Migration {
|
||||
appVersion = '<1.6.0';
|
||||
async up() {
|
||||
const rolesDesktopRoutesRepo = this.db.getRepository('rolesDesktopRoutes');
|
||||
const count = await rolesDesktopRoutesRepo.count();
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
const desktopRoutesRepo = this.db.getRepository('desktopRoutes');
|
||||
|
||||
try {
|
||||
await this.db.sequelize.transaction(async (transaction) => {
|
||||
const rolesDesktopRoutes = await rolesDesktopRoutesRepo.find({ transaction });
|
||||
const rolesDesktopRouteIds = rolesDesktopRoutes.map((x) => x.get('desktopRouteId'));
|
||||
const desktopRoutes = await desktopRoutesRepo.find({
|
||||
filter: { $or: [{ id: rolesDesktopRouteIds }, { parentId: rolesDesktopRouteIds }] },
|
||||
transaction,
|
||||
});
|
||||
const records = findMissingRoutes(rolesDesktopRoutes, desktopRoutes);
|
||||
await rolesDesktopRoutesRepo.createMany({ records, transaction });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findMissingRoutes(rolesDesktopRoutes, desktopRoutes) {
|
||||
const routeMap = _.keyBy(desktopRoutes, 'id');
|
||||
const group = (id, roleName) => `${id}_${roleName}`;
|
||||
const existingMap = new Set(rolesDesktopRoutes.map((x) => group(x.desktopRouteId, x.roleName)));
|
||||
|
||||
const missingRoutes = [];
|
||||
rolesDesktopRoutes.forEach((route) => {
|
||||
const parentRoute = routeMap[route.desktopRouteId];
|
||||
|
||||
desktopRoutes.forEach((child) => {
|
||||
if (child.hidden && child.parentId === parentRoute.id) {
|
||||
const key = group(child.id, route.roleName);
|
||||
if (!existingMap.has(key)) {
|
||||
missingRoutes.push({ desktopRouteId: child.id, roleName: route.roleName });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return missingRoutes;
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Model } from '@nocobase/database';
|
||||
import { Model, Transaction } from '@nocobase/database';
|
||||
import PluginLocalizationServer from '@nocobase/plugin-localization';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import { tval } from '@nocobase/utils';
|
||||
@ -16,6 +16,7 @@ import { resolve } from 'path';
|
||||
import { getAntdLocale } from './antd';
|
||||
import { getCronLocale } from './cron';
|
||||
import { getCronstrueLocale } from './cronstrue';
|
||||
import _ from 'lodash';
|
||||
|
||||
async function getLang(ctx) {
|
||||
const SystemSetting = ctx.db.getRepository('systemSettings');
|
||||
@ -196,6 +197,47 @@ export class PluginClientServer extends Plugin {
|
||||
transaction,
|
||||
});
|
||||
});
|
||||
|
||||
const processRoleDesktopRoutes = async (params: {
|
||||
models: Model[];
|
||||
action: 'create' | 'remove';
|
||||
transaction: Transaction;
|
||||
}) => {
|
||||
const { models, action, transaction } = params;
|
||||
if (!models.length) return;
|
||||
const parentIds = models.map((x) => x.desktopRouteId);
|
||||
const tabs: Model[] = await this.app.db.getRepository('desktopRoutes').find({
|
||||
where: { parentId: parentIds, hidden: true },
|
||||
transaction,
|
||||
});
|
||||
if (!tabs.length) return;
|
||||
const repository = this.app.db.getRepository('rolesDesktopRoutes');
|
||||
const roleName = models[0].get('roleName');
|
||||
const tabIds = tabs.map((x) => x.get('id'));
|
||||
const where = { desktopRouteId: tabIds, roleName };
|
||||
if (action === 'create') {
|
||||
const exists = await repository.find({ where });
|
||||
const modelsByRouteId = _.keyBy(exists, (x) => x.get('desktopRouteId'));
|
||||
const createModels = tabs
|
||||
.map((x) => !modelsByRouteId[x.get('id')] && { desktopRouteId: x.get('id'), roleName })
|
||||
.filter(Boolean);
|
||||
return await repository.create({ values: createModels, transaction });
|
||||
}
|
||||
|
||||
if (action === 'remove') {
|
||||
return await repository.destroy({ filter: where, transaction });
|
||||
}
|
||||
};
|
||||
this.app.db.on('rolesDesktopRoutes.afterBulkCreate', async (instances, options) => {
|
||||
await processRoleDesktopRoutes({ models: instances, action: 'create', transaction: options.transaction });
|
||||
});
|
||||
|
||||
this.app.db.on('rolesDesktopRoutes.afterBulkDestroy', async (options) => {
|
||||
const models = await this.app.db.getRepository('rolesDesktopRoutes').find({
|
||||
where: options.where,
|
||||
});
|
||||
await processRoleDesktopRoutes({ models: models, action: 'remove', transaction: options.transaction });
|
||||
});
|
||||
}
|
||||
|
||||
registerActionHandlers() {
|
||||
|
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { MockServer, createMockServer } from '@nocobase/test';
|
||||
import Migration from '../migrations/202503091240-fix-roles-mobile-routes';
|
||||
import { vi, describe, beforeEach, afterEach, test, expect } from 'vitest';
|
||||
import { Model, Repository } from '@nocobase/database';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('202503091240-fix-roles-mobile-routes', () => {
|
||||
let app: MockServer, role1: Model, role2: Model, mobileRoutesRepo: Repository, rolesMobileRoutesRepo: Repository;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['nocobase'],
|
||||
});
|
||||
await app.version.update('1.5.0');
|
||||
role1 = await app.db.getRepository('roles').create({
|
||||
values: {
|
||||
name: 'role1',
|
||||
},
|
||||
});
|
||||
role2 = await app.db.getRepository('roles').create({
|
||||
values: {
|
||||
name: 'role2',
|
||||
},
|
||||
});
|
||||
mobileRoutesRepo = app.db.getRepository('mobileRoutes');
|
||||
rolesMobileRoutesRepo = app.db.getRepository('rolesMobileRoutes');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
async function createTestData() {
|
||||
const idSet = new Set();
|
||||
while (idSet.size < 8) {
|
||||
idSet.add(Math.ceil(10 + Math.random() * 10));
|
||||
}
|
||||
const ids = Array.from(idSet);
|
||||
const records = [
|
||||
{ id: ids[0], title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[1], title: 'tabs1', type: 'tabs', parentId: ids[0], hideInMenu: null, hidden: true },
|
||||
{ id: ids[2], title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[3], title: 'tabs3', type: 'tabs', parentId: ids[2], hideInMenu: null, hidden: true },
|
||||
{ id: ids[4], title: 'page4', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: ids[5], title: 'tabs4', type: 'tabs', parentId: ids[4], hideInMenu: null, hidden: null },
|
||||
{ id: ids[6], title: 'tabs5', type: 'tabs', parentId: ids[4], hideInMenu: null, hidden: null },
|
||||
];
|
||||
return await mobileRoutesRepo.createMany({ records });
|
||||
}
|
||||
it('should create miss child routes', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesMobileRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), mobileRouteId: models[0].id },
|
||||
{ roleName: role2.get('name'), mobileRouteId: models[2].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesMobileRoutes.length).toBe(4);
|
||||
const role1Routes = await rolesMobileRoutes.filter((x) => x.roleName === role1.get('name'));
|
||||
expect(role1Routes.map((x) => x.get('mobileRouteId'))).toContain(models[0].id);
|
||||
expect(role1Routes.map((x) => x.get('mobileRouteId'))).toContain(models[1].id);
|
||||
const role2Routes = await rolesMobileRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).toContain(models[2].id);
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).toContain(models[3].id);
|
||||
});
|
||||
|
||||
it('should create miss child routes when has repeat data', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesMobileRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), mobileRouteId: models[0].id },
|
||||
{ roleName: role1.get('name'), mobileRouteId: models[1].id },
|
||||
{ roleName: role2.get('name'), mobileRouteId: models[2].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesMobileRoutes.length).toBe(4);
|
||||
const role1Routes = await rolesMobileRoutes.filter((x) => x.roleName === role1.get('name'));
|
||||
expect(role1Routes.map((x) => x.get('mobileRouteId'))).toContain(models[0].id);
|
||||
expect(role1Routes.map((x) => x.get('mobileRouteId'))).toContain(models[1].id);
|
||||
const role2Routes = await rolesMobileRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).toContain(models[2].id);
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).toContain(models[3].id);
|
||||
});
|
||||
|
||||
it('should create miss child routes no includes hidden = false', async () => {
|
||||
const models = await createTestData();
|
||||
await rolesMobileRoutesRepo.create({
|
||||
values: [
|
||||
{ roleName: role1.get('name'), mobileRouteId: models[0].id },
|
||||
{ roleName: role1.get('name'), mobileRouteId: models[2].id },
|
||||
{ roleName: role2.get('name'), mobileRouteId: models[4].id },
|
||||
],
|
||||
});
|
||||
const migration = new Migration({ db: app.db, app } as any);
|
||||
await migration.up();
|
||||
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({
|
||||
where: { roleName: [role1.get('name'), role2.get('name')] },
|
||||
});
|
||||
expect(rolesMobileRoutes.map((x) => x.get('mobileRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([models[1].id, models[3].id]),
|
||||
);
|
||||
const role2Routes = await rolesMobileRoutes.filter((x) => x.roleName === role2.get('name'));
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).not.toContain(models[5].id);
|
||||
expect(role2Routes.map((x) => x.get('mobileRouteId'))).not.toContain(models[6].id);
|
||||
});
|
||||
});
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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 Database, { Model, Repository } from '@nocobase/database';
|
||||
import { createMockServer, MockServer } from '@nocobase/test';
|
||||
|
||||
describe('mobileRoutes', async () => {
|
||||
let app: MockServer, db: Database, mobileRoutesRepo: Repository, rolesMobileRoutesRepo: Repository;
|
||||
let role: Model;
|
||||
beforeEach(async () => {
|
||||
app = await createMockServer({
|
||||
plugins: ['error-handler', 'mobile', 'field-sort', 'acl', 'data-source-main', 'data-source-manager'],
|
||||
});
|
||||
db = app.db;
|
||||
mobileRoutesRepo = db.getRepository('mobileRoutes');
|
||||
rolesMobileRoutesRepo = db.getRepository('rolesMobileRoutes');
|
||||
role = await db.getRepository('roles').create({ values: { name: 'role1' } });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it(`should rolesMobileRoutes includes tabs when create menu`, async () => {
|
||||
const records = [
|
||||
{ id: 50, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 51, title: 'tabs1', type: 'tabs', parentId: 50, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await mobileRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesMobileRoutes').model.bulkCreate([{ roleName: role.get('name'), mobileRouteId: 50 }]);
|
||||
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesMobileRoutes.length).toBe(2);
|
||||
expect(rolesMobileRoutes.map((x) => x.get('mobileRouteId'))).toStrictEqual(expect.arrayContaining([50, 51]));
|
||||
});
|
||||
|
||||
it(`should rolesMobileRoutes includes tabs when bulk create menu`, async () => {
|
||||
const records = [
|
||||
{ id: 52, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 53, title: 'tabs1', type: 'tabs', parentId: 52, hideInMenu: null, hidden: true },
|
||||
{ id: 54, title: 'page2', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 55, title: 'tabs2', type: 'tabs', parentId: 54, hideInMenu: null, hidden: true },
|
||||
{ id: 56, title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 57, title: 'tabs3', type: 'tabs', parentId: 56, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await mobileRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesMobileRoutes').model.bulkCreate([
|
||||
{ roleName: role.get('name'), mobileRouteId: 52 },
|
||||
{ roleName: role.get('name'), mobileRouteId: 54 },
|
||||
]);
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesMobileRoutes.length).toStrictEqual(4);
|
||||
expect(rolesMobileRoutes.map((x) => x.get('mobileRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([52, 53, 54, 55]),
|
||||
);
|
||||
});
|
||||
|
||||
it(`should rolesMobileRoutes includes tabs when create menu and invalid data`, async () => {
|
||||
const records = [
|
||||
{ id: 60, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 61, title: 'tabs1', type: 'tabs', parentId: 60, hideInMenu: null, hidden: true },
|
||||
{ id: 62, title: 'page3', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 63, title: 'tabs3', type: 'tabs', parentId: 62, hideInMenu: null, hidden: null },
|
||||
{ id: 64, title: 'tabs4', type: 'tabs', parentId: 62, hideInMenu: null, hidden: null },
|
||||
];
|
||||
await mobileRoutesRepo.createMany({ records });
|
||||
await rolesMobileRoutesRepo.create({ values: { roleName: role.get('name'), mobileRouteId: 61 } });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesMobileRoutes').model.bulkCreate([
|
||||
{ roleName: role.get('name'), mobileRouteId: 60 },
|
||||
{ roleName: role.get('name'), mobileRouteId: 62 },
|
||||
{ roleName: role.get('name'), mobileRouteId: 63 },
|
||||
]);
|
||||
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesMobileRoutes.length).toBe(4);
|
||||
expect(rolesMobileRoutes.map((x) => x.get('mobileRouteId'))).toStrictEqual(
|
||||
expect.arrayContaining([60, 61, 62, 63]),
|
||||
);
|
||||
});
|
||||
|
||||
it(`should rolesMobileRoutes destroy tabs when remove menu and invalid data`, async () => {
|
||||
const records = [
|
||||
{ id: 70, title: 'page1', type: 'page', hideInMenu: null, hidden: null },
|
||||
{ id: 71, title: 'tabs1', type: 'tabs', parentId: 70, hideInMenu: null, hidden: true },
|
||||
];
|
||||
await mobileRoutesRepo.createMany({ records });
|
||||
// trigger bulkCreate hooks
|
||||
await db.getCollection('rolesMobileRoutes').model.bulkCreate([{ roleName: role.get('name'), mobileRouteId: 70 }]);
|
||||
|
||||
let rolesMobileRoutes = await rolesMobileRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesMobileRoutes.length).toBe(2);
|
||||
expect(rolesMobileRoutes.map((x) => x.get('mobileRouteId'))).toStrictEqual(expect.arrayContaining([70, 71]));
|
||||
|
||||
const transaction = await db.sequelize.transaction();
|
||||
await db.getCollection('rolesMobileRoutes').model.destroy({
|
||||
where: { roleName: role.get('name'), mobileRouteId: [70] },
|
||||
individualHooks: false,
|
||||
transaction,
|
||||
});
|
||||
await transaction.commit();
|
||||
rolesMobileRoutes = await rolesMobileRoutesRepo.find({ where: { roleName: role.get('name') } });
|
||||
expect(rolesMobileRoutes.length).toBe(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class extends Migration {
|
||||
appVersion = '<1.6.0';
|
||||
async up() {
|
||||
const rolesMobileRoutesRepo = this.db.getRepository('rolesMobileRoutes');
|
||||
const count = await rolesMobileRoutesRepo.count();
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
const mobileRoutesRepo = this.db.getRepository('mobileRoutes');
|
||||
|
||||
try {
|
||||
await this.db.sequelize.transaction(async (transaction) => {
|
||||
const rolesMobileRoutes = await rolesMobileRoutesRepo.find({ transaction });
|
||||
const rolesMobileRouteIds = rolesMobileRoutes.map((x) => x.get('mobileRouteId'));
|
||||
const mobileRoutes = await mobileRoutesRepo.find({
|
||||
filter: { $or: [{ id: rolesMobileRouteIds }, { parentId: rolesMobileRouteIds }] },
|
||||
transaction,
|
||||
});
|
||||
const records = findMissingRoutes(rolesMobileRoutes, mobileRoutes);
|
||||
await rolesMobileRoutesRepo.createMany({ records, transaction });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findMissingRoutes(rolesMobileRoutes, mobileRoutes) {
|
||||
const routeMap = _.keyBy(mobileRoutes, 'id');
|
||||
const group = (id, roleName) => `${id}_${roleName}`;
|
||||
const existingMap = new Set(rolesMobileRoutes.map((x) => group(x.mobileRouteId, x.roleName)));
|
||||
|
||||
const missingRoutes = [];
|
||||
rolesMobileRoutes.forEach((route) => {
|
||||
const parentRoute = routeMap[route.mobileRouteId];
|
||||
|
||||
mobileRoutes.forEach((child) => {
|
||||
if (child.hidden && child.parentId === parentRoute.id) {
|
||||
const key = group(child.id, route.roleName);
|
||||
if (!existingMap.has(key)) {
|
||||
missingRoutes.push({ mobileRouteId: child.id, roleName: route.roleName });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return missingRoutes;
|
||||
}
|
@ -7,10 +7,11 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Model } from '@nocobase/database';
|
||||
import { Model, Transaction } from '@nocobase/database';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import PluginLocalizationServer from '@nocobase/plugin-localization';
|
||||
import { tval } from '@nocobase/utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class PluginMobileServer extends Plugin {
|
||||
async load() {
|
||||
@ -70,6 +71,46 @@ export class PluginMobileServer extends Plugin {
|
||||
transaction,
|
||||
});
|
||||
});
|
||||
const processRoleMobileRoutes = async (params: {
|
||||
models: Model[];
|
||||
action: 'create' | 'remove';
|
||||
transaction: Transaction;
|
||||
}) => {
|
||||
const { models, action, transaction } = params;
|
||||
if (!models.length) return;
|
||||
const parentIds = models.map((x) => x.mobileRouteId);
|
||||
const tabs: Model[] = await this.app.db.getRepository('mobileRoutes').find({
|
||||
where: { parentId: parentIds, hidden: true },
|
||||
transaction,
|
||||
});
|
||||
if (!tabs.length) return;
|
||||
const repository = this.app.db.getRepository('rolesMobileRoutes');
|
||||
const roleName = models[0].get('roleName');
|
||||
const tabIds = tabs.map((x) => x.get('id'));
|
||||
const where = { mobileRouteId: tabIds, roleName };
|
||||
if (action === 'create') {
|
||||
const exists = await repository.find({ where });
|
||||
const modelsByRouteId = _.keyBy(exists, (x) => x.get('mobileRouteId'));
|
||||
const createModels = tabs
|
||||
.map((x) => !modelsByRouteId[x.get('id')] && { mobileRouteId: x.get('id'), roleName })
|
||||
.filter(Boolean);
|
||||
return await repository.create({ values: createModels, transaction });
|
||||
}
|
||||
|
||||
if (action === 'remove') {
|
||||
return await repository.destroy({ filter: where, transaction });
|
||||
}
|
||||
};
|
||||
this.app.db.on('rolesMobileRoutes.afterBulkCreate', async (instances, options) => {
|
||||
await processRoleMobileRoutes({ models: instances, action: 'create', transaction: options.transaction });
|
||||
});
|
||||
|
||||
this.app.db.on('rolesMobileRoutes.afterBulkDestroy', async (options) => {
|
||||
const models = await this.app.db.getRepository('rolesMobileRoutes').find({
|
||||
where: options.where,
|
||||
});
|
||||
await processRoleMobileRoutes({ models: models, action: 'remove', transaction: options.transaction });
|
||||
});
|
||||
}
|
||||
|
||||
registerActionHandlers() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user