- 新增迁移 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 个不一致
238 lines
7.7 KiB
Rust
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(())
|
|
}
|
|
}
|