DTO 字段级验证: - version 字段全部添加 range(min=0) 防止负数 - 标签内容验证: 单个标签最长 30 字符,不允许空白 - 班级码正则: 仅允许字母数字,拒绝特殊字符 - 贴纸包 price 添加 range(min=0) 防止负价格 - thumbnail_url/image_url 添加 length(max=500) 限制 - 同步请求 data payload 限制 1MB/条 Handler validate() 调用补齐: - delete_journal: DeleteJournalReq 添加 Validate derive + handler 调用 - bind_child / unbind_child / delete_child_data: 补齐 req.validate() 调用 - join_class: 添加 validate_code() 字母数字检查 - sync_journals: 添加 validate_changes_data() payload 大小检查 审计覆盖: 5a-C01/02/03 + 5a-H02/03/04 + B-03 + 7b-C02
57 lines
1.6 KiB
Rust
57 lines
1.6 KiB
Rust
// 日记同步 API 处理器
|
|
|
|
use axum::extract::{Extension, FromRef, State};
|
|
use axum::response::Json;
|
|
use validator::Validate;
|
|
|
|
use erp_core::error::AppError;
|
|
use erp_core::rbac::require_permission;
|
|
use erp_core::types::{ApiResponse, TenantContext};
|
|
|
|
use crate::dto::SyncReq;
|
|
use crate::dto::SyncResp;
|
|
use crate::service::sync_service::SyncService;
|
|
use crate::state::DiaryState;
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/v1/diary/sync",
|
|
request_body = SyncReq,
|
|
responses(
|
|
(status = 200, description = "同步成功", body = ApiResponse<SyncResp>),
|
|
(status = 401, description = "未授权"),
|
|
(status = 403, description = "权限不足"),
|
|
(status = 409, description = "存在版本冲突"),
|
|
),
|
|
security(("bearer_auth" = [])),
|
|
tag = "日记同步"
|
|
)]
|
|
/// POST /api/v1/diary/sync
|
|
///
|
|
/// 日记同步端点。客户端提交本地变更,服务端返回服务端变更和冲突列表。
|
|
/// 需要 `diary.journal.read` 权限。
|
|
pub async fn sync_journals<S>(
|
|
State(state): State<DiaryState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<SyncReq>,
|
|
) -> Result<Json<ApiResponse<SyncResp>>, AppError>
|
|
where
|
|
DiaryState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
req.validate().map_err(|e| AppError::Validation(e.to_string()))?;
|
|
req.validate_changes_data().map_err(AppError::Validation)?;
|
|
require_permission(&ctx, "diary.journal.read")?;
|
|
|
|
let resp = SyncService::sync(
|
|
ctx.tenant_id,
|
|
ctx.user_id,
|
|
req.last_sync_time,
|
|
req.changes,
|
|
&state.db,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(ApiResponse::ok(resp)))
|
|
}
|