Files
teatea-pension/modules/core/server/permissions.ts

292 lines
11 KiB
TypeScript

import { and, eq, inArray, isNull, or } from "drizzle-orm";
import { getDatabase } from "@/modules/core/server/db";
import { organizations, permissions, rolePermissions, roles } from "@/modules/core/server/schema";
import { ensureSystemSettings } from "@/modules/core/server/system-settings";
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: "care:read", label: "查看护理任务", category: "护理", description: "查看护理服务任务、执行状态和记录。" },
{ id: "care:manage", label: "处理护理任务", category: "护理", description: "启动、完成和维护护理服务执行记录。" },
{ id: "health:read", label: "查看健康数据", category: "健康", description: "查看老人健康档案、生命体征和异常复核。" },
{ id: "health: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",
"care:read",
"care:manage",
"health:read",
"health:manage",
"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",
"care:read",
"care:manage",
"health:read",
"health:manage",
"facility:read",
"facility:manage",
"admission:read",
"admission:manage",
"elder:read",
"elder:create",
"elder:update",
"elder:delete",
],
},
{
key: "caregiver",
scope: "organization",
description: "照护人员,查看床位和维护老人照护信息。",
permissions: ["care:read", "care:manage", "health:read", "health:manage", "facility:read", "admission:read", "elder:read", "elder:update"],
},
{
key: "viewer",
scope: "organization",
description: "普通用户,只读查看老人、床位和入住信息。",
permissions: ["care:read", "health:read", "facility:read", "admission:read", "elder:read"],
},
{
key: "family",
scope: "organization",
description: "亲属账号,预留给家属端与授权老人动态访问。",
permissions: [],
},
{
key: "resident",
scope: "organization",
description: "本人账号,预留给老人本人查看个人服务与健康信息。",
permissions: [],
},
];
export async function ensureSystemDefaults(): Promise<void> {
const database = getDatabase();
await ensureSystemSettings();
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();
}
const organizationRows = await database.select({ id: organizations.id }).from(organizations);
await Promise.all(organizationRows.map((organization) => seedOrganizationRoles(organization.id)));
}
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);
}