feat(小宝预警): 增加权限与数据键
This commit is contained in:
@@ -9,6 +9,8 @@ export const APP_DATA_KEYS = [
|
|||||||
'task-categories',
|
'task-categories',
|
||||||
'task-worklogs',
|
'task-worklogs',
|
||||||
'work-activities',
|
'work-activities',
|
||||||
|
'xiaobao-risk-insights',
|
||||||
|
'xiaobao-risk-snapshots',
|
||||||
'overtime',
|
'overtime',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,15 @@ export const PERMISSION_GROUPS: PermissionGroup[] = [
|
|||||||
{ action: 'export', label: '导出', permission: 'overtime:export' },
|
{ action: 'export', label: '导出', permission: 'overtime:export' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
module: 'xiaobao.warning',
|
||||||
|
moduleLabel: '小宝预警',
|
||||||
|
category: 'main',
|
||||||
|
actions: [
|
||||||
|
{ action: 'view', label: '查看', permission: 'xiaobao.warning:view' },
|
||||||
|
{ action: 'manage', label: '管理', permission: 'xiaobao.warning:manage' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{ module: 'member', moduleLabel: '成员', category: 'main', actions: std4('member') },
|
{ module: 'member', moduleLabel: '成员', category: 'main', actions: std4('member') },
|
||||||
{ module: 'role', moduleLabel: '角色', category: 'main', actions: std4('role') },
|
{ module: 'role', moduleLabel: '角色', category: 'main', actions: std4('role') },
|
||||||
{ module: 'version.req', moduleLabel: '需求 Tab', category: 'version_tab', actions: stdTab('version.req') },
|
{ module: 'version.req', moduleLabel: '需求 Tab', category: 'version_tab', actions: stdTab('version.req') },
|
||||||
@@ -67,6 +76,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
...std4('requirement').map((a) => a.permission),
|
...std4('requirement').map((a) => a.permission),
|
||||||
'version.req:view', 'version.req:manage',
|
'version.req:view', 'version.req:manage',
|
||||||
'version.product_plan:view', 'version.product_plan:manage',
|
'version.product_plan:view', 'version.product_plan:manage',
|
||||||
|
'xiaobao.warning:view', 'xiaobao.warning:manage',
|
||||||
'overtime:view', 'member:view', 'role:view',
|
'overtime:view', 'member:view', 'role:view',
|
||||||
'version.research:view', 'version.ui_plan:view', 'version.devtask:view',
|
'version.research:view', 'version.ui_plan:view', 'version.devtask:view',
|
||||||
'version.testcase:view', 'version.bug:view',
|
'version.testcase:view', 'version.bug:view',
|
||||||
@@ -76,6 +86,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'version.devtask:view', 'version.devtask:manage',
|
'version.devtask:view', 'version.devtask:manage',
|
||||||
'version.bug:view', 'version.bug:edit',
|
'version.bug:view', 'version.bug:edit',
|
||||||
'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.testcase:view',
|
'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.testcase:view',
|
||||||
|
'xiaobao.warning:view',
|
||||||
'overtime:view', 'overtime:create',
|
'overtime:view', 'overtime:create',
|
||||||
],
|
],
|
||||||
'role-test': [
|
'role-test': [
|
||||||
@@ -83,6 +94,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'version.testcase:view', 'version.testcase:manage',
|
'version.testcase:view', 'version.testcase:manage',
|
||||||
'version.bug:view', 'version.bug:create', 'version.bug:edit', 'version.bug:delete',
|
'version.bug:view', 'version.bug:create', 'version.bug:edit', 'version.bug:delete',
|
||||||
'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.devtask:view',
|
'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.devtask:view',
|
||||||
|
'xiaobao.warning:view',
|
||||||
'overtime:view', 'overtime:create',
|
'overtime:view', 'overtime:create',
|
||||||
],
|
],
|
||||||
'role-design': [
|
'role-design': [
|
||||||
@@ -90,6 +102,7 @@ export const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'version.ui_plan:view', 'version.ui_plan:manage',
|
'version.ui_plan:view', 'version.ui_plan:manage',
|
||||||
'version.research:view', 'version.product_plan:view', 'version.devtask:view',
|
'version.research:view', 'version.product_plan:view', 'version.devtask:view',
|
||||||
'version.testcase:view', 'version.bug:view',
|
'version.testcase:view', 'version.bug:view',
|
||||||
|
'xiaobao.warning:view',
|
||||||
'overtime:view', 'overtime:create',
|
'overtime:view', 'overtime:create',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
38
apps/web/lib/role-permission-migration.test.ts
Normal file
38
apps/web/lib/role-permission-migration.test.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import type { RoleItem } from './members';
|
||||||
|
import { mergePresetRolePermissions } from './role-permission-migration';
|
||||||
|
|
||||||
|
test('mergePresetRolePermissions adds new preset permissions to existing product manager role', () => {
|
||||||
|
const roles: RoleItem[] = [
|
||||||
|
{
|
||||||
|
id: 'role-pm',
|
||||||
|
name: '产品经理',
|
||||||
|
createdAt: '2024-01-01',
|
||||||
|
permissions: ['product:view'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = mergePresetRolePermissions(roles);
|
||||||
|
|
||||||
|
assert.equal(result.changed, true);
|
||||||
|
assert.ok(result.roles[0].permissions.includes('product:view'));
|
||||||
|
assert.ok(result.roles[0].permissions.includes('xiaobao.warning:view'));
|
||||||
|
assert.ok(result.roles[0].permissions.includes('xiaobao.warning:manage'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mergePresetRolePermissions keeps custom role permissions unchanged', () => {
|
||||||
|
const roles: RoleItem[] = [
|
||||||
|
{
|
||||||
|
id: 'role-custom',
|
||||||
|
name: '自定义角色',
|
||||||
|
createdAt: '2024-01-01',
|
||||||
|
permissions: ['version:view'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = mergePresetRolePermissions(roles);
|
||||||
|
|
||||||
|
assert.equal(result.changed, false);
|
||||||
|
assert.deepEqual(result.roles[0].permissions, ['version:view']);
|
||||||
|
});
|
||||||
15
apps/web/lib/role-permission-migration.ts
Normal file
15
apps/web/lib/role-permission-migration.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { RoleItem } from './members';
|
||||||
|
import { DEFAULT_ROLE_PERMISSIONS } from './permissions';
|
||||||
|
|
||||||
|
export function mergePresetRolePermissions(roles: RoleItem[]): { roles: RoleItem[]; changed: boolean } {
|
||||||
|
let changed = false;
|
||||||
|
const next = roles.map((role) => {
|
||||||
|
const defaults = DEFAULT_ROLE_PERMISSIONS[role.id];
|
||||||
|
if (!defaults || role.permissions.includes('*')) return role;
|
||||||
|
const merged = Array.from(new Set([...role.permissions, ...defaults]));
|
||||||
|
if (merged.length === role.permissions.length) return role;
|
||||||
|
changed = true;
|
||||||
|
return { ...role, permissions: merged };
|
||||||
|
});
|
||||||
|
return { roles: next, changed };
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ export type ServerDataKey =
|
|||||||
| 'task-categories'
|
| 'task-categories'
|
||||||
| 'task-worklogs'
|
| 'task-worklogs'
|
||||||
| 'work-activities'
|
| 'work-activities'
|
||||||
|
| 'xiaobao-risk-insights'
|
||||||
|
| 'xiaobao-risk-snapshots'
|
||||||
| 'overtime';
|
| 'overtime';
|
||||||
|
|
||||||
interface ServerDataResponse<T> {
|
interface ServerDataResponse<T> {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
sanitizeSystemAdminPatch,
|
sanitizeSystemAdminPatch,
|
||||||
} from '@/lib/member-system';
|
} from '@/lib/member-system';
|
||||||
import { DEFAULT_ROLE_PERMISSIONS } from '@/lib/permissions';
|
import { DEFAULT_ROLE_PERMISSIONS } from '@/lib/permissions';
|
||||||
|
import { mergePresetRolePermissions } from '@/lib/role-permission-migration';
|
||||||
import { loadServerData, saveServerData } from '@/lib/server-data';
|
import { loadServerData, saveServerData } from '@/lib/server-data';
|
||||||
|
|
||||||
const PRESET_DEPARTMENTS: Department[] = [
|
const PRESET_DEPARTMENTS: Department[] = [
|
||||||
@@ -81,13 +82,16 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
const permissionMerged = mergePresetRolePermissions(migratedRoles);
|
||||||
|
const rolesWithNewDefaults = permissionMerged.roles;
|
||||||
const adminRole = PRESET_ROLES.find((r) => r.id === SYSTEM_ADMIN_ROLE_ID)!;
|
const adminRole = PRESET_ROLES.find((r) => r.id === SYSTEM_ADMIN_ROLE_ID)!;
|
||||||
const roles = migratedRoles.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID)
|
const roles = rolesWithNewDefaults.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID)
|
||||||
? migratedRoles.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r)
|
? rolesWithNewDefaults.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r)
|
||||||
: [adminRole, ...migratedRoles];
|
: [adminRole, ...rolesWithNewDefaults];
|
||||||
const rolesChanged =
|
const rolesChanged =
|
||||||
roles.length !== migratedRoles.length ||
|
permissionMerged.changed ||
|
||||||
roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(migratedRoles[index]));
|
roles.length !== rolesWithNewDefaults.length ||
|
||||||
|
roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(rolesWithNewDefaults[index]));
|
||||||
const ensured = ensureSystemAdminMember(cached.members);
|
const ensured = ensureSystemAdminMember(cached.members);
|
||||||
const next = {
|
const next = {
|
||||||
departments: cached.departments,
|
departments: cached.departments,
|
||||||
|
|||||||
Reference in New Issue
Block a user