// 同步协议模型 — 与 Rust 端 SyncReq/SyncResp/SyncChange/ConflictInfo 一一对应 // // 端点: POST /api/v1/diary/sync // Rust DTO: crates/erp-diary/src/dto.rs (SyncReq, SyncResp, SyncChange, ConflictInfo) /// 同步请求 — 与 Rust SyncReq 对应 /// /// ```rust /// pub struct SyncReq { /// pub last_sync_time: Option>, /// pub changes: Vec, /// } /// ``` class SyncReq { final DateTime? lastSyncTime; final List changes; const SyncReq({this.lastSyncTime, this.changes = const []}); Map toJson() => { if (lastSyncTime != null) 'last_sync_time': lastSyncTime!.toUtc().toIso8601String(), 'changes': changes.map((c) => c.toJson()).toList(), }; } /// 同步变更条目 — 与 Rust SyncChange 枚举对应 /// /// ```rust /// pub enum SyncChange { /// CreateJournal { data: serde_json::Value }, /// UpdateJournal { id: Uuid, version: i32, data: serde_json::Value }, /// DeleteJournal { id: Uuid, version: i32 }, /// } /// ``` sealed class SyncChange { const SyncChange(); Map toJson(); /// 从 JSON 反序列化 factory SyncChange.fromJson(Map json) { if (json.containsKey('CreateJournal')) { return SyncChangeCreateJournal( data: json['CreateJournal']['data'] as Map, ); } if (json.containsKey('UpdateJournal')) { final inner = json['UpdateJournal'] as Map; return SyncChangeUpdateJournal( id: inner['id'] as String, version: inner['version'] as int, data: inner['data'] as Map, ); } if (json.containsKey('DeleteJournal')) { final inner = json['DeleteJournal'] as Map; return SyncChangeDeleteJournal( id: inner['id'] as String, version: inner['version'] as int, ); } throw FormatException('Unknown SyncChange variant: $json'); } } /// 创建日记变更 class SyncChangeCreateJournal extends SyncChange { final Map data; const SyncChangeCreateJournal({required this.data}); @override Map toJson() => { 'CreateJournal': {'data': data}, }; } /// 更新日记变更 class SyncChangeUpdateJournal extends SyncChange { final String id; final int version; final Map data; const SyncChangeUpdateJournal({ required this.id, required this.version, required this.data, }); @override Map toJson() => { 'UpdateJournal': { 'id': id, 'version': version, 'data': data, }, }; } /// 删除日记变更 class SyncChangeDeleteJournal extends SyncChange { final String id; final int version; const SyncChangeDeleteJournal({ required this.id, required this.version, }); @override Map toJson() => { 'DeleteJournal': { 'id': id, 'version': version, }, }; } /// 同步响应 — 与 Rust SyncResp 对应 /// /// ```rust /// pub struct SyncResp { /// pub server_changes: Vec, /// pub conflicts: Vec, /// pub sync_time: DateTime, /// } /// ``` class SyncResp { final List> serverChanges; final List conflicts; final DateTime syncTime; const SyncResp({ required this.serverChanges, required this.conflicts, required this.syncTime, }); factory SyncResp.fromJson(Map json) => SyncResp( serverChanges: (json['server_changes'] as List) .map((e) => Map.from(e as Map)) .toList(), conflicts: (json['conflicts'] as List) .map((e) => ConflictInfo.fromJson(Map.from(e as Map))) .toList(), syncTime: DateTime.parse(json['sync_time'] as String), ); } /// 冲突信息 — 与 Rust ConflictInfo 对应 /// /// ```rust /// pub struct ConflictInfo { /// pub journal_id: Uuid, /// pub local_version: i32, /// pub server_version: i32, /// } /// ``` class ConflictInfo { final String journalId; final int localVersion; final int serverVersion; const ConflictInfo({ required this.journalId, required this.localVersion, required this.serverVersion, }); factory ConflictInfo.fromJson(Map json) => ConflictInfo( journalId: json['journal_id'] as String, localVersion: json['local_version'] as int, serverVersion: json['server_version'] as int, ); }