boarddown_core/crdt/
changeset.rs1use serde::{Deserialize, Serialize};
2
3use crate::Version;
4use boarddown_schema::{TaskOp, Operation, TaskBuilder};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Changeset {
8 pub board_id: boarddown_schema::BoardId,
9 pub from_version: Version,
10 pub to_version: Version,
11 pub operations: Vec<TaskOp>,
12 pub timestamp: chrono::DateTime<chrono::Utc>,
13}
14
15impl Changeset {
16 pub fn new(board_id: boarddown_schema::BoardId, from_version: Version) -> Self {
17 Self {
18 board_id,
19 from_version,
20 to_version: from_version.increment(),
21 operations: Vec::new(),
22 timestamp: chrono::Utc::now(),
23 }
24 }
25
26 pub fn add_operation(&mut self, op: TaskOp) {
27 self.operations.push(op);
28 }
29
30 pub fn is_empty(&self) -> bool {
31 self.operations.is_empty()
32 }
33
34 pub fn apply(&self, board: &mut boarddown_schema::Board) -> Result<(), crate::Error> {
35 for task_op in &self.operations {
36 match &task_op.operation {
37 Operation::Create { title, column } => {
38 let task = TaskBuilder::default()
39 .id(task_op.task_id.clone())
40 .title(title.clone())
41 .column(column.clone())
42 .build()
43 .map_err(|e| crate::Error::Validation(e))?;
44 board.tasks.insert(task.id.clone(), task);
45 }
46 Operation::Delete => {
47 board.tasks.remove(&task_op.task_id);
48 }
49 _ => {
50 if let Some(task) = board.tasks.get_mut(&task_op.task_id) {
51 task_op.operation.apply_to_task(task);
52 }
53 }
54 }
55 }
56 board.updated_at = chrono::Utc::now();
57 Ok(())
58 }
59
60 pub fn merge(&self, other: &Self) -> Result<Self, crate::Error> {
61 let mut all_ops: Vec<TaskOp> = self.operations.iter()
62 .chain(other.operations.iter())
63 .cloned()
64 .collect();
65
66 all_ops.sort_by_key(|op| op.timestamp);
67
68 let max_version = self.to_version.0.max(other.to_version.0);
69
70 Ok(Self {
71 board_id: self.board_id.clone(),
72 from_version: Version(max_version),
73 to_version: Version(max_version + 1),
74 operations: all_ops,
75 timestamp: chrono::Utc::now(),
76 })
77 }
78}