feat(diary): 手写引擎 + 日记 CRUD + 同步 API (Phase F3 + B2)
Flutter 手写引擎 (Phase F3): - stroke_model.dart: 笔画数据模型 (StrokePoint/Stroke/BrushType) - stroke_renderer.dart: perfect_freehand 渲染管线 + 四画笔参数 - handwriting_canvas.dart: Listener 输入 + 掌心抑制 + 去抖过滤 - editor_bloc.dart: BLoC 状态管理 + 撤销/重做 (50步) Rust 日记 CRUD + 同步 (Phase B2): - journal_service.rs: CRUD + 软删除 + 分页列表 + 事件发布 - sync_service.rs: 版本号同步 + 冲突检测 - journal_handler.rs: 5个API端点 + utoipa注解 + 权限守卫 - sync_handler.rs: 同步API端点 - error.rs: From<DiaryError> for AppError + 8个单元测试 - 路由注册: /diary/journals + /diary/sync 验证: - cargo check: 0 error - cargo test: 433 测试全通过 - flutter analyze: 1 warning (unused private param)
This commit is contained in:
53
crates/erp-diary/src/handler/sync_handler.rs
Normal file
53
crates/erp-diary/src/handler/sync_handler.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
// 日记同步 API 处理器
|
||||
|
||||
use axum::extract::{Extension, FromRef, State};
|
||||
use axum::response::Json;
|
||||
|
||||
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,
|
||||
{
|
||||
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)))
|
||||
}
|
||||
Reference in New Issue
Block a user