263 lines
9.5 KiB
TypeScript
263 lines
9.5 KiB
TypeScript
import { and, eq, inArray, isNull, or } from "drizzle-orm";
|
|
|
|
import { getDatabase } from "@/modules/core/server/db";
|
|
import { permissions, rolePermissions, roles } from "@/modules/core/server/schema";
|
|
import type { Permission, RoleDefinition, RoleId } from "@/modules/core/types";
|
|
import { PERMISSIONS, ROLE_LABELS } from "@/modules/core/types";
|
|
|
|
type PermissionDefinition = {
|
|
id: Permission;
|
|
label: string;
|
|
category: string;
|
|
description: string;
|
|
};
|
|
|
|
export const PERMISSION_DEFINITIONS: PermissionDefinition[] = [
|
|
{ id: "platform:manage", label: "平台管理", category: "平台", description: "管理平台级配置和跨机构操作。" },
|
|
{ id: "organization:read", label: "查看机构", category: "机构", description: "查看机构、院区和租户信息。" },
|
|
{ id: "organization:manage", label: "管理机构", category: "机构", description: "创建、编辑和停用机构。" },
|
|
{ id: "account:read", label: "查看用户", category: "用户", description: "查看账号、成员和申请。" },
|
|
{ id: "account:manage", label: "管理用户", category: "用户", description: "创建、审批、启停和分配用户。" },
|
|
{ id: "role:read", label: "查看角色", category: "权限", description: "查看角色和权限矩阵。" },
|
|
{ id: "role:manage", label: "管理角色", category: "权限", description: "创建自定义角色并配置权限组合。" },
|
|
{ id: "permission:read", label: "查看权限", category: "权限", description: "查看系统注册权限点。" },
|
|
{ id: "audit:read", label: "查看审计", category: "审计", description: "查看关键操作审计日志。" },
|
|
{ id: "incident:read", label: "查看故障", category: "运维", description: "查看系统健康和故障中心。" },
|
|
{ id: "incident:manage", label: "处理故障", category: "运维", description: "确认、处理和关闭故障事件。" },
|
|
{ id: "facility:read", label: "查看房间床位", category: "床位", description: "查看房间、床位和占用状态。" },
|
|
{ id: "facility:manage", label: "管理房间床位", category: "床位", description: "维护院区、房间、床位和床位状态。" },
|
|
{ id: "admission:read", label: "查看入住", category: "入住", description: "查看入住、换床、退住历史。" },
|
|
{ id: "admission:manage", label: "管理入住", category: "入住", description: "办理入住、换床和退住。" },
|
|
{ id: "elder:read", label: "查看老人档案", category: "老人", description: "查看老人基础档案。" },
|
|
{ id: "elder:create", label: "新增老人档案", category: "老人", description: "创建老人档案。" },
|
|
{ id: "elder:update", label: "更新老人档案", category: "老人", description: "更新老人档案和照护信息。" },
|
|
{ id: "elder:delete", label: "删除老人档案", category: "老人", description: "删除老人档案。" },
|
|
];
|
|
|
|
type SeedRole = {
|
|
key: RoleId;
|
|
scope: "platform" | "organization";
|
|
description: string;
|
|
permissions: Permission[];
|
|
};
|
|
|
|
export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
|
|
{
|
|
key: "platform_admin",
|
|
scope: "platform",
|
|
description: "平台最高权限,管理所有机构、角色、审计和故障中心。",
|
|
permissions: [...PERMISSIONS],
|
|
},
|
|
{
|
|
key: "platform_operator",
|
|
scope: "platform",
|
|
description: "平台运营角色,管理机构和跨机构用户配置。",
|
|
permissions: [
|
|
"organization:read",
|
|
"organization:manage",
|
|
"account:read",
|
|
"account:manage",
|
|
"role:read",
|
|
"permission:read",
|
|
"audit:read",
|
|
"incident:read",
|
|
],
|
|
},
|
|
{
|
|
key: "platform_auditor",
|
|
scope: "platform",
|
|
description: "平台审计角色,只读查看机构、审计和故障。",
|
|
permissions: ["organization:read", "account:read", "role:read", "permission:read", "audit:read", "incident:read"],
|
|
},
|
|
{
|
|
key: "platform_ops",
|
|
scope: "platform",
|
|
description: "平台运维角色,处理系统故障和运行状态。",
|
|
permissions: ["organization:read", "audit:read", "incident:read", "incident:manage"],
|
|
},
|
|
{
|
|
key: "org_admin",
|
|
scope: "organization",
|
|
description: "机构管理员,管理本机构用户、角色、房间床位、入住和审计。",
|
|
permissions: [
|
|
"organization:read",
|
|
"account:read",
|
|
"account:manage",
|
|
"role:read",
|
|
"role:manage",
|
|
"permission:read",
|
|
"audit:read",
|
|
"incident:read",
|
|
"facility:read",
|
|
"facility:manage",
|
|
"admission:read",
|
|
"admission:manage",
|
|
"elder:read",
|
|
"elder:create",
|
|
"elder:update",
|
|
"elder:delete",
|
|
],
|
|
},
|
|
{
|
|
key: "manager",
|
|
scope: "organization",
|
|
description: "运营主管,处理老人档案、床位入住和审计查看。",
|
|
permissions: [
|
|
"account:read",
|
|
"role:read",
|
|
"audit:read",
|
|
"facility:read",
|
|
"facility:manage",
|
|
"admission:read",
|
|
"admission:manage",
|
|
"elder:read",
|
|
"elder:create",
|
|
"elder:update",
|
|
"elder:delete",
|
|
],
|
|
},
|
|
{
|
|
key: "caregiver",
|
|
scope: "organization",
|
|
description: "照护人员,查看床位和维护老人照护信息。",
|
|
permissions: ["facility:read", "admission:read", "elder:read", "elder:update"],
|
|
},
|
|
{
|
|
key: "viewer",
|
|
scope: "organization",
|
|
description: "只读访客,查看老人、床位和入住信息。",
|
|
permissions: ["facility:read", "admission:read", "elder:read"],
|
|
},
|
|
];
|
|
|
|
export async function ensureSystemDefaults(): Promise<void> {
|
|
const database = getDatabase();
|
|
|
|
await database
|
|
.insert(permissions)
|
|
.values(PERMISSION_DEFINITIONS)
|
|
.onConflictDoUpdate({
|
|
target: permissions.id,
|
|
set: {
|
|
label: permissions.label,
|
|
category: permissions.category,
|
|
description: permissions.description,
|
|
isEnabled: true,
|
|
},
|
|
});
|
|
|
|
const existingRoles = await database.select().from(roles).where(isNull(roles.organizationId));
|
|
const existingRoleKeys = new Set(existingRoles.map((role) => role.key));
|
|
const seedRoles = SYSTEM_ROLE_DEFINITIONS.filter((role) => role.scope === "platform" && !existingRoleKeys.has(role.key));
|
|
|
|
if (seedRoles.length > 0) {
|
|
await database.insert(roles).values(
|
|
seedRoles.map((role) => ({
|
|
key: role.key,
|
|
scope: role.scope,
|
|
label: ROLE_LABELS[role.key],
|
|
description: role.description,
|
|
isSystem: true,
|
|
isEnabled: true,
|
|
})),
|
|
);
|
|
}
|
|
|
|
const platformRoles = await database.select().from(roles).where(isNull(roles.organizationId));
|
|
const inserts = platformRoles.flatMap((role) => {
|
|
const definition = SYSTEM_ROLE_DEFINITIONS.find((item) => item.key === role.key);
|
|
if (!definition) {
|
|
return [];
|
|
}
|
|
|
|
return definition.permissions.map((permissionId) => ({
|
|
roleId: role.id,
|
|
permissionId,
|
|
}));
|
|
});
|
|
|
|
if (inserts.length > 0) {
|
|
await database.insert(rolePermissions).values(inserts).onConflictDoNothing();
|
|
}
|
|
}
|
|
|
|
export async function seedOrganizationRoles(organizationId: string): Promise<void> {
|
|
const database = getDatabase();
|
|
const organizationRoles = SYSTEM_ROLE_DEFINITIONS.filter((role) => role.scope === "organization");
|
|
|
|
await database
|
|
.insert(roles)
|
|
.values(
|
|
organizationRoles.map((role) => ({
|
|
key: role.key,
|
|
organizationId,
|
|
scope: role.scope,
|
|
label: ROLE_LABELS[role.key],
|
|
description: role.description,
|
|
isSystem: true,
|
|
isEnabled: true,
|
|
})),
|
|
)
|
|
.onConflictDoNothing();
|
|
|
|
const seededRoles = await database
|
|
.select()
|
|
.from(roles)
|
|
.where(and(eq(roles.organizationId, organizationId), inArray(roles.key, organizationRoles.map((role) => role.key))));
|
|
|
|
const inserts = seededRoles.flatMap((role) => {
|
|
const definition = SYSTEM_ROLE_DEFINITIONS.find((item) => item.key === role.key);
|
|
if (!definition) {
|
|
return [];
|
|
}
|
|
|
|
return definition.permissions.map((permissionId) => ({ roleId: role.id, permissionId }));
|
|
});
|
|
|
|
if (inserts.length > 0) {
|
|
await database.insert(rolePermissions).values(inserts).onConflictDoNothing();
|
|
}
|
|
}
|
|
|
|
export async function getRoleDefinitions(organizationId?: string): Promise<RoleDefinition[]> {
|
|
const database = getDatabase();
|
|
const rows = await database
|
|
.select()
|
|
.from(roles)
|
|
.where(organizationId ? or(isNull(roles.organizationId), eq(roles.organizationId, organizationId)) : isNull(roles.organizationId));
|
|
|
|
const roleIds = rows.map((role) => role.id);
|
|
const permissionRows =
|
|
roleIds.length > 0
|
|
? await database.select().from(rolePermissions).where(inArray(rolePermissions.roleId, roleIds))
|
|
: [];
|
|
|
|
return rows.map((role) => ({
|
|
id: role.id,
|
|
key: role.key,
|
|
label: role.label,
|
|
description: role.description,
|
|
scope: role.scope,
|
|
organizationId: role.organizationId ?? undefined,
|
|
isSystem: role.isSystem,
|
|
isEnabled: role.isEnabled,
|
|
permissions: permissionRows
|
|
.filter((permission) => permission.roleId === role.id)
|
|
.map((permission) => permission.permissionId as Permission),
|
|
}));
|
|
}
|
|
|
|
export async function getPermissionsForRole(roleId: string): Promise<Permission[]> {
|
|
const database = getDatabase();
|
|
const rows = await database
|
|
.select({ permissionId: rolePermissions.permissionId })
|
|
.from(rolePermissions)
|
|
.innerJoin(roles, eq(roles.id, rolePermissions.roleId))
|
|
.where(and(eq(rolePermissions.roleId, roleId), eq(roles.isEnabled, true)));
|
|
return rows.map((row) => row.permissionId as Permission);
|
|
}
|
|
|
|
export function hasPermission(permissionsList: readonly Permission[], permission: Permission): boolean {
|
|
return permissionsList.includes(permission);
|
|
}
|