fix(health): 穷尽审计修复 — 权限同步/编译错误/前端bug/审计日志
审计发现并修复的问题: 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:
@@ -77,16 +77,22 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteArticleReq {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn delete_article<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
Json(req): Json<DeleteArticleReq>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.articles.manage")?;
|
||||
article_service::delete_article(&state, ctx.tenant_id, id, Some(ctx.user_id)).await?;
|
||||
article_service::delete_article(&state, ctx.tenant_id, id, Some(ctx.user_id), req.version).await?;
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -9,6 +9,7 @@ use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::dto::doctor_dto::*;
|
||||
use crate::dto::DeleteWithVersion;
|
||||
use crate::service::doctor_service;
|
||||
use crate::state::HealthState;
|
||||
|
||||
@@ -28,11 +29,6 @@ pub struct UpdateDoctorWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn list_doctors<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -9,6 +9,7 @@ use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::dto::follow_up_dto::*;
|
||||
use crate::dto::DeleteWithVersion;
|
||||
use crate::service::follow_up_service;
|
||||
use crate::state::HealthState;
|
||||
|
||||
@@ -36,11 +37,6 @@ pub struct UpdateFollowUpTaskWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn list_tasks<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -9,7 +9,9 @@ use erp_core::rbac::require_permission;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::dto::health_data_dto::*;
|
||||
use crate::dto::DeleteWithVersion;
|
||||
use crate::service::health_data_service;
|
||||
use crate::service::trend_service;
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -34,11 +36,6 @@ pub struct GenerateTrendReq {
|
||||
pub period_end: chrono::NaiveDate,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateWithVersion<T> {
|
||||
pub data: T,
|
||||
@@ -299,7 +296,7 @@ where
|
||||
require_permission(&ctx, "health.health-data.list")?;
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let result = health_data_service::list_trends(
|
||||
let result = trend_service::list_trends(
|
||||
&state, ctx.tenant_id, patient_id, page, page_size,
|
||||
)
|
||||
.await?;
|
||||
@@ -317,7 +314,7 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.health-data.manage")?;
|
||||
let result = health_data_service::generate_trend(
|
||||
let result = trend_service::generate_trend(
|
||||
&state, ctx.tenant_id, patient_id, Some(ctx.user_id), req.period_start, req.period_end,
|
||||
)
|
||||
.await?;
|
||||
@@ -335,7 +332,7 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.health-data.list")?;
|
||||
let result = health_data_service::get_indicator_timeseries(
|
||||
let result = trend_service::get_indicator_timeseries(
|
||||
&state, ctx.tenant_id, patient_id, indicator, params.start_date, params.end_date,
|
||||
)
|
||||
.await?;
|
||||
@@ -356,7 +353,7 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.health-data.list")?;
|
||||
let result = health_data_service::get_mini_trend(
|
||||
let result = trend_service::get_mini_trend(
|
||||
&state, ctx.tenant_id, ctx.user_id, params.indicator, params.range,
|
||||
)
|
||||
.await?;
|
||||
@@ -376,7 +373,7 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.health-data.list")?;
|
||||
let result = health_data_service::get_mini_today(
|
||||
let result = trend_service::get_mini_today(
|
||||
&state, ctx.tenant_id, ctx.user_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::dto::patient_dto::{
|
||||
CreatePatientReq, FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp,
|
||||
UpdatePatientReq,
|
||||
};
|
||||
use crate::dto::DeleteWithVersion;
|
||||
use crate::service::patient_service;
|
||||
use crate::state::HealthState;
|
||||
|
||||
@@ -30,11 +31,6 @@ pub struct AssignDoctorReq {
|
||||
pub relationship_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteWithVersion {
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn list_patients<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -278,7 +274,7 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.manage")?;
|
||||
patient_service::remove_doctor(&state, ctx.tenant_id, patient_id, doctor_id).await?;
|
||||
patient_service::remove_doctor(&state, ctx.tenant_id, patient_id, doctor_id, Some(ctx.user_id)).await?;
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user