Phase 1 安全热修复: - P0-1: /uploads 文件服务添加 JWT 认证中间件(支持 header + query param) - P0-2: analytics/batch 路由从 public 移到 protected_routes - P0-3: plugin engine SQL 注入修复(format! → 参数化查询) - P0-new: stats_service compute_avg_field 字段白名单 + FLOAT8 类型转换 Phase 2 数据完整性: - P0-4: 组织删除级联检查(添加部门存在性校验) - P0-5: 部门删除级联检查(添加岗位 + 用户存在性校验) - P0-8: workflow on_tenant_deleted 实现 5 实体批量删除 - P0-7: 并行网关 race condition 修复(consumed → completed 原子转换) Phase 3 P1 后端 Bug: - P1-12: plugin host 表名消毒(使用 sanitize_identifier) - P1-10: workflow deprecated 状态转换(published → deprecated) - P1-11: workflow 更新验证条件(nodes/edges 任一变化即验证) - P0-9: 小程序 .gitignore 添加 .env/.env.*/日志 - P1-19: 小程序加密密钥替换为 64 字符强密钥 Phase 4 消息模块: - P1-5: 通知偏好 GET 路由 + handler - P1-4: 消息模板 update/delete CRUD + version - P2-8: mark_all_read SQL 添加 version + 1 - P2-7: markAsRead 改为乐观更新 + 失败回滚 Phase 5 前端修复: - P2-9: 通知面板点击导航到 /messages - P2-1: 随访任务患者名批量 ID 解析(替代 UUID 显示) - P2-5: AppointmentList 分离 patient_id/doctor_id 分别调用 API - P2-17: PluginMarket installed 字段修正(name → id) - P3-3: 路由标题 fallback 改为模式匹配(支持 :id 动态路径) - P2-15: workflow updateDefinition 添加 version 字段 - P3-9: Kanban 版本使用记录实际 version - P2-21: secure-storage 生产环境无密钥时阻止存储 - P3-11: destroyOnHidden → destroyOnClose - P3-13: PendingTasks 深色模式 Tag 颜色适配 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
1.9 KiB
Rust
61 lines
1.9 KiB
Rust
use axum::Json;
|
|
use axum::extract::FromRef;
|
|
use axum::extract::{Extension, State};
|
|
|
|
use erp_core::error::AppError;
|
|
use erp_core::types::{ApiResponse, TenantContext};
|
|
|
|
use crate::dto::UpdateSubscriptionReq;
|
|
use crate::message_state::MessageState;
|
|
use crate::service::subscription_service::SubscriptionService;
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/v1/message-subscriptions",
|
|
responses(
|
|
(status = 200, description = "成功", body = ApiResponse<crate::dto::MessageSubscriptionResp>),
|
|
(status = 401, description = "未授权"),
|
|
),
|
|
security(("bearer_auth" = [])),
|
|
tag = "消息订阅"
|
|
)]
|
|
/// 获取当前用户的消息订阅偏好。
|
|
pub async fn get_subscription<S>(
|
|
State(_state): State<MessageState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<crate::dto::MessageSubscriptionResp>>, AppError>
|
|
where
|
|
MessageState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
let resp = SubscriptionService::get(ctx.tenant_id, ctx.user_id, &_state.db).await?;
|
|
Ok(Json(ApiResponse::ok(resp)))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
put,
|
|
path = "/api/v1/message-subscriptions",
|
|
request_body = UpdateSubscriptionReq,
|
|
responses(
|
|
(status = 200, description = "成功", body = ApiResponse<crate::dto::MessageSubscriptionResp>),
|
|
(status = 401, description = "未授权"),
|
|
(status = 403, description = "权限不足"),
|
|
),
|
|
security(("bearer_auth" = [])),
|
|
tag = "消息订阅"
|
|
)]
|
|
/// 更新消息订阅偏好。
|
|
pub async fn update_subscription<S>(
|
|
State(_state): State<MessageState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<UpdateSubscriptionReq>,
|
|
) -> Result<Json<ApiResponse<crate::dto::MessageSubscriptionResp>>, AppError>
|
|
where
|
|
MessageState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
let resp = SubscriptionService::upsert(ctx.tenant_id, ctx.user_id, &req, &_state.db).await?;
|
|
|
|
Ok(Json(ApiResponse::ok(resp)))
|
|
}
|