Files
hms/crates/erp-server/migration/src/m20260513_000145_seed_missing_permissions.rs
iven c681049c82 fix(db,ci): 补全 26 个缺失权限码 seed 注册 + 检查脚本增强
- 新增迁移 000144 全实体乐观锁 version 字段强制化
- 新增迁移 000145 注册 26 个后端已声明但 seed 缺失的权限码
  (ai.analysis/prompt/suggestion/usage/provider, copilot.insights/risk/rules,
   health.ble-gateways/critical-alerts/devices/family-proxy/shifts 等)
- check-permissions.sh: 增加 module.rs PermissionDescriptor 提取,
  支持两段式权限码 (plugin.admin/tenant.manage)
- CI 检查结果: Check 1 PASS, Check 2 PASS, 0 个不一致
2026-05-13 14:30:27 +08:00

238 lines
7.7 KiB
Rust

//! 补全缺失权限码注册
//!
//! CI check-permissions.sh 发现 23 个后端 handler 已使用但 seed 迁移未注册的权限码。
//! 本迁移统一补注册到 permissions 表,并绑定 admin 角色。
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
let sys = "00000000-0000-0000-0000-000000000000";
// (code, name, resource, action)
let permissions: &[(&str, &str, &str, &str)] = &[
// AI 模块
("ai.analysis.list", "查看 AI 分析", "ai", "analysis.list"),
(
"ai.analysis.manage",
"管理 AI 分析",
"ai",
"analysis.manage",
),
("ai.prompt.list", "查看 AI 提示词", "ai", "prompt.list"),
("ai.prompt.manage", "管理 AI 提示词", "ai", "prompt.manage"),
(
"ai.provider.manage",
"管理 AI Provider",
"ai",
"provider.manage",
),
(
"ai.suggestion.list",
"查看 AI 建议",
"ai",
"suggestion.list",
),
(
"ai.suggestion.manage",
"管理 AI 建议",
"ai",
"suggestion.manage",
),
("ai.usage.list", "查看 AI 用量", "ai", "usage.list"),
// Copilot
(
"copilot.insights.list",
"查看 Copilot 洞察",
"copilot",
"insights.list",
),
(
"copilot.insights.manage",
"管理 Copilot 洞察",
"copilot",
"insights.manage",
),
("copilot.risk.view", "查看风险快照", "copilot", "risk.view"),
(
"copilot.rules.list",
"查看 Copilot 规则",
"copilot",
"rules.list",
),
(
"copilot.rules.manage",
"管理 Copilot 规则",
"copilot",
"rules.manage",
),
// Health — IoT/设备
(
"health.ble-gateways.manage",
"管理 BLE 网关",
"health",
"ble-gateways.manage",
),
(
"health.critical-alerts.manage",
"管理危急值告警",
"health",
"critical-alerts.manage",
),
(
"health.critical-value-thresholds.manage",
"管理危急值阈值",
"health",
"critical-value-thresholds.manage",
),
(
"health.device-readings.manage",
"管理设备读数",
"health",
"device-readings.manage",
),
(
"health.devices.manage",
"管理患者设备",
"health",
"devices.manage",
),
(
"health.medication-reminders.manage",
"管理药物提醒",
"health",
"medication-reminders.manage",
),
// Health — 其他
(
"health.family-proxy.list",
"查看家庭健康代理",
"health",
"family-proxy.list",
),
(
"health.family-proxy.manage",
"管理家庭健康代理",
"health",
"family-proxy.manage",
),
(
"health.oauth.manage",
"管理 OAuth 合作方",
"health",
"oauth.manage",
),
(
"health.shifts.manage",
"管理班次",
"health",
"shifts.manage",
),
// Plugin / Tenant
("plugin.admin", "插件管理", "plugin", "admin"),
("plugin.list", "查看插件列表", "plugin", "list"),
("tenant.manage", "租户管理", "tenant", "manage"),
];
for &(code, name, resource, action) in permissions {
db.execute_unprepared(&format!(
r#"
INSERT INTO permissions (id, tenant_id, code, name, resource, action, description,
created_at, updated_at, created_by, updated_by, deleted_at, version)
SELECT gen_random_uuid(), t.id, '{code}', '{name}', '{resource}', '{action}', '{name}',
NOW(), NOW(), '{sys}', '{sys}', NULL, 1
FROM tenant t
WHERE NOT EXISTS (
SELECT 1 FROM permissions p
WHERE p.code = '{code}' AND p.tenant_id = t.id AND p.deleted_at IS NULL
)
"#
)).await?;
}
// 绑定所有新权限到 admin 角色
let codes: Vec<&str> = permissions.iter().map(|p| p.0).collect();
let codes_sql = codes
.iter()
.map(|c| format!("'{c}'"))
.collect::<Vec<_>>()
.join(", ");
db.execute_unprepared(&format!(
r#"
INSERT INTO role_permissions (role_id, permission_id, tenant_id, created_by, updated_by, version)
SELECT r.id, p.id, t.id, r.id, r.id, 1
FROM tenant t
JOIN roles r ON r.tenant_id = t.id AND r.code = 'admin' AND r.deleted_at IS NULL
JOIN permissions p ON p.tenant_id = t.id
AND p.code IN ({codes_sql})
AND p.deleted_at IS NULL
WHERE NOT EXISTS (
SELECT 1 FROM role_permissions rp
WHERE rp.permission_id = p.id AND rp.role_id = r.id
)
"#
)).await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
let codes = [
"ai.analysis.list",
"ai.analysis.manage",
"ai.prompt.list",
"ai.prompt.manage",
"ai.provider.manage",
"ai.suggestion.list",
"ai.suggestion.manage",
"ai.usage.list",
"copilot.insights.list",
"copilot.insights.manage",
"copilot.risk.view",
"copilot.rules.list",
"copilot.rules.manage",
"health.ble-gateways.manage",
"health.critical-alerts.manage",
"health.critical-value-thresholds.manage",
"health.device-readings.manage",
"health.devices.manage",
"health.family-proxy.list",
"health.family-proxy.manage",
"health.medication-reminders.manage",
"health.oauth.manage",
"health.shifts.manage",
"plugin.admin",
"plugin.list",
"tenant.manage",
];
let codes_sql = codes
.iter()
.map(|c| format!("'{c}'"))
.collect::<Vec<_>>()
.join(", ");
db.execute_unprepared(&format!(
"DELETE FROM role_permissions WHERE permission_id IN \
(SELECT id FROM permissions WHERE code IN ({codes_sql}))"
))
.await
.ok();
db.execute_unprepared(&format!(
"DELETE FROM permissions WHERE code IN ({codes_sql})"
))
.await
.ok();
Ok(())
}
}