fix(health): 客户试用前全局审计修复 — P0 权限旁路 + API 路径 + 事件注册
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

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:
iven
2026-05-04 11:02:25 +08:00
parent cde3a863a2
commit 30a578ee00
16 changed files with 260 additions and 17 deletions

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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(

View File

@@ -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),