feat: Iteration 1 — 审计日志IP记录、文件上传、医护端API、小程序角色切换
Iteration 1 六项任务全部完成: 1. 审计日志IP记录 — task_local RequestInfo 自动注入 IP/user_agent 2. 文件上传服务 — multipart 上传 + ServeDir 静态文件服务 3. 医护端后端API — 医生工作台仪表盘 + 患者标签CRUD + 会话已读 4. 小程序角色切换 — 登录后根据角色跳转医护台/患者首页 5. 小程序安全加固 — secure-storage 开发模式警告 6. 讨论记录归档 — docs/discussions/
This commit is contained in:
@@ -188,3 +188,64 @@ where
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
/// 标记会话消息为已读。
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/consultation-sessions/{id}/read",
|
||||
responses(
|
||||
(status = 200, description = "标记成功"),
|
||||
(status = 404, description = "会话不存在"),
|
||||
),
|
||||
tag = "咨询管理",
|
||||
)]
|
||||
pub async fn mark_session_read<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
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 role = if is_doctor { "doctor" } else { "patient" };
|
||||
consultation_service::mark_session_read(
|
||||
&state, ctx.tenant_id, id, ctx.user_id, role,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
/// 获取当前医生的仪表盘数据。
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/doctor/dashboard",
|
||||
responses(
|
||||
(status = 200, description = "仪表盘数据"),
|
||||
),
|
||||
tag = "医护端",
|
||||
)]
|
||||
pub async fn get_doctor_dashboard<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
) -> Result<Json<ApiResponse<consultation_service::DoctorDashboard>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.consultation.list")?;
|
||||
let result = consultation_service::get_doctor_dashboard(
|
||||
&state, ctx.tenant_id, ctx.user_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
@@ -319,3 +319,74 @@ where
|
||||
let tags = patient_service::list_tags(&state, ctx.tenant_id).await?;
|
||||
Ok(Json(ApiResponse::ok(tags)))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreateTagReq {
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn create_tag<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(req): Json<CreateTagReq>,
|
||||
) -> Result<Json<ApiResponse<patient_service::TagResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.manage")?;
|
||||
let result = patient_service::create_tag(
|
||||
&state, ctx.tenant_id, Some(ctx.user_id),
|
||||
patient_service::CreateTagReq {
|
||||
name: req.name, color: req.color, description: req.description,
|
||||
},
|
||||
).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateTagWithVersion {
|
||||
pub name: Option<String>,
|
||||
pub color: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn update_tag<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<UpdateTagWithVersion>,
|
||||
) -> Result<Json<ApiResponse<patient_service::TagResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.manage")?;
|
||||
let result = patient_service::update_tag(
|
||||
&state, ctx.tenant_id, id, Some(ctx.user_id),
|
||||
patient_service::UpdateTagReq {
|
||||
name: req.name, color: req.color, description: req.description, version: req.version,
|
||||
},
|
||||
).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
pub async fn delete_tag<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<DeleteWithVersion>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.manage")?;
|
||||
patient_service::delete_tag(
|
||||
&state, ctx.tenant_id, id, Some(ctx.user_id), req.version,
|
||||
).await?;
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user