fix(health): 客户试用前全局审计修复 — P0 权限旁路 + API 路径 + 事件注册
P0 阻塞修复:
- 修复 PrivateRoute 权限旁路: p.startsWith('auth.') 匹配不到任何权限码,
改为基于实际权限码的路由级检查 (user.manage/role.manage/organization.manage)
- 修复 deviceReadings API 路径: /patients/{id}/device-readings/daily 改为
/vital-signs/daily?patient_id=, 消除 404
P1 重要修复:
- 补全事件注册表: 新增 auth(11) + config(8) + workflow(4) + plugin(2) = 25 条
- article_article_tag 联表新增 tenant_id + deleted_at + 审计列 (迁移 107)
- vital_signs_hourly 新增 deleted_at 支持软删除过滤 (迁移 108)
- 6 个页面添加权限守卫 (AlertDashboard/AlertRuleList/DeviceManage/
AiAnalysisList/AiUsageDashboard)
- DialysisModule 声明 auth 依赖
This commit is contained in:
@@ -80,6 +80,10 @@ impl ErpModule for DialysisModule {
|
||||
ModuleType::Builtin
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<&str> {
|
||||
vec!["auth"]
|
||||
}
|
||||
|
||||
fn permissions(&self) -> Vec<PermissionDescriptor> {
|
||||
vec![
|
||||
PermissionDescriptor {
|
||||
|
||||
@@ -9,6 +9,11 @@ pub struct Model {
|
||||
pub article_id: Uuid,
|
||||
#[sea_orm(primary_key)]
|
||||
pub tag_id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -16,6 +16,8 @@ pub struct Model {
|
||||
pub max_val: Option<f64>,
|
||||
pub avg_val: f64,
|
||||
pub sample_count: i32,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
pub version: i32,
|
||||
|
||||
@@ -337,7 +337,7 @@ pub async fn create_article(
|
||||
let m = active.insert(&state.db).await?;
|
||||
|
||||
// 保存标签关联
|
||||
save_article_tags(state, m.id, &req.tag_ids).await?;
|
||||
save_article_tags(state, tenant_id, m.id, &req.tag_ids).await?;
|
||||
|
||||
audit_service::record(
|
||||
AuditLog::new(tenant_id, operator_id, "article.created", "article")
|
||||
@@ -384,7 +384,7 @@ pub async fn update_article(
|
||||
|
||||
// 替换标签关联
|
||||
if let Some(tag_ids) = req.tag_ids {
|
||||
replace_article_tags(state, m.id, &tag_ids).await?;
|
||||
replace_article_tags(state, tenant_id, m.id, &tag_ids).await?;
|
||||
}
|
||||
|
||||
audit_service::record(
|
||||
@@ -537,24 +537,29 @@ async fn batch_load_article_tags(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn save_article_tags(state: &HealthState, article_id: Uuid, tag_ids: &[Uuid]) -> HealthResult<()> {
|
||||
async fn save_article_tags(state: &HealthState, tenant_id: Uuid, article_id: Uuid, tag_ids: &[Uuid]) -> HealthResult<()> {
|
||||
let now = chrono::Utc::now();
|
||||
for tid in tag_ids {
|
||||
let active = article_article_tag::ActiveModel {
|
||||
article_id: Set(article_id),
|
||||
tag_id: Set(*tid),
|
||||
tenant_id: Set(tenant_id),
|
||||
deleted_at: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
};
|
||||
active.insert(&state.db).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn replace_article_tags(state: &HealthState, article_id: Uuid, tag_ids: &[Uuid]) -> HealthResult<()> {
|
||||
async fn replace_article_tags(state: &HealthState, tenant_id: Uuid, article_id: Uuid, tag_ids: &[Uuid]) -> HealthResult<()> {
|
||||
article_article_tag::Entity::delete_many()
|
||||
.filter(article_article_tag::Column::ArticleId.eq(article_id))
|
||||
.exec(&state.db)
|
||||
.await?;
|
||||
|
||||
save_article_tags(state, article_id, tag_ids).await
|
||||
save_article_tags(state, tenant_id, article_id, tag_ids).await
|
||||
}
|
||||
|
||||
async fn save_revision(
|
||||
|
||||
@@ -326,6 +326,7 @@ async fn upsert_hourly_aggregates(
|
||||
max_val: Set(max_val),
|
||||
avg_val: Set(avg_val),
|
||||
sample_count: Set(sample_count),
|
||||
deleted_at: Set(None),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
version: Set(1),
|
||||
|
||||
@@ -106,6 +106,8 @@ mod m20260502_000103_seed_follow_up_template_menu;
|
||||
mod m20260504_000104_create_vital_signs_daily;
|
||||
mod m20260504_000105_alter_patient_devices_add_status;
|
||||
mod m20260504_000106_create_api_clients;
|
||||
mod m20260504_000107_alter_article_article_tag_add_tenant_and_soft_delete;
|
||||
mod m20260504_000108_alter_vital_signs_hourly_add_soft_delete;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -219,6 +221,8 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260504_000104_create_vital_signs_daily::Migration),
|
||||
Box::new(m20260504_000105_alter_patient_devices_add_status::Migration),
|
||||
Box::new(m20260504_000106_create_api_clients::Migration),
|
||||
Box::new(m20260504_000107_alter_article_article_tag_add_tenant_and_soft_delete::Migration),
|
||||
Box::new(m20260504_000108_alter_vital_signs_hourly_add_soft_delete::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
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> {
|
||||
// 添加 tenant_id 列(可为空,后续由应用层填充)
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.add_column(ColumnDef::new(Alias::new("tenant_id")).uuid().null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 添加 deleted_at 列
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 添加 created_at / updated_at(如果不存在)
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 从关联的 article 表回填 tenant_id
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(
|
||||
"UPDATE article_article_tag aat SET tenant_id = (SELECT tenant_id FROM article WHERE article.id = aat.article_id) WHERE aat.tenant_id IS NULL",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 设置 tenant_id 为 NOT NULL(回填完成后)
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.modify_column(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 添加索引
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_article_article_tag_tenant_id")
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_article_article_tag_tenant_id")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("article_article_tag"))
|
||||
.drop_column(Alias::new("updated_at"))
|
||||
.drop_column(Alias::new("created_at"))
|
||||
.drop_column(Alias::new("deleted_at"))
|
||||
.drop_column(Alias::new("tenant_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.drop_column(Alias::new("deleted_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user