fix(health): 穷尽审计修复 — 权限同步/编译错误/前端bug/审计日志
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / security-audit (push) Has been cancelled

审计发现并修复的问题:

HIGH:
- H1: ConsultationDetail 使用 getSession(id) 替代错误的列表搜索
- H2: SessionResp 添加 version/updated_at 字段
- H3: 移除 FollowUpRecordList 调用不存在的导出端点
- H4: 新增 articles.ts 前端 API 模块

MEDIUM:
- M1: article delete 添加乐观锁 (expected_version)
- M2: 取消预约排班释放传播错误 (log::warn -> ?)
- M3: FollowUpTaskList 日期格式 Dayjs -> string
- M4: 补充 15 个缺失审计日志

LOW:
- L1: 替换 follow_up_service 中的 .unwrap()
- L2: PatientListItem 添加 version 字段

CRITICAL (新发现):
- 权限未同步: 健康模块 14 个权限从未写入数据库,添加启动时自动同步
- migration 表名错误: patients -> patient
- 编译错误: health_trend entity 未导入, ToPrimitive trait 未导入
- HealthError 缺少 From<AppError> 实现
This commit is contained in:
iven
2026-04-25 08:58:58 +08:00
parent 9ffb938128
commit 07f4ba41ba
31 changed files with 3373 additions and 445 deletions

View File

@@ -1,5 +1,6 @@
use axum::Extension;
use axum::extract::{FromRef, Json, Path, Query, State};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use serde::Deserialize;
use utoipa::IntoParams;
use uuid::Uuid;
@@ -44,6 +45,8 @@ pub struct ExportSessionsParams {
pub status: Option<String>,
pub patient_id: Option<Uuid>,
pub doctor_id: Option<Uuid>,
pub page: Option<u64>,
pub page_size: Option<u64>,
}
pub async fn create_session<S>(
@@ -83,6 +86,20 @@ where
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_session<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
) -> Result<Json<ApiResponse<SessionResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let result = consultation_service::get_session(&state, ctx.tenant_id, id).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn list_messages<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
@@ -131,10 +148,18 @@ where
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.manage")?;
let is_doctor = crate::entity::doctor_profile::Entity::find()
.filter(crate::entity::doctor_profile::Column::UserId.eq(ctx.user_id))
.filter(crate::entity::doctor_profile::Column::TenantId.eq(ctx.tenant_id))
.filter(crate::entity::doctor_profile::Column::DeletedAt.is_null())
.one(&state.db)
.await
.map_err(|e| AppError::Internal(e.to_string()))?
.is_some();
let mut msg_req = CreateMessageReq {
session_id: req.session_id,
sender_id: ctx.user_id,
sender_role: "doctor".to_string(),
sender_role: if is_doctor { "doctor" } else { "patient" }.to_string(),
content_type: req.content_type,
content: req.content,
};
@@ -150,7 +175,7 @@ pub async fn export_sessions<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(params): Query<ExportSessionsParams>,
) -> Result<Json<ApiResponse<Vec<SessionResp>>>, AppError>
) -> Result<Json<ApiResponse<PaginatedResponse<SessionResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
@@ -158,6 +183,7 @@ where
require_permission(&ctx, "health.consultation.list")?;
let result = consultation_service::export_sessions(
&state, ctx.tenant_id, params.status, params.patient_id, params.doctor_id,
params.page, params.page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))