haystack_server/ops/
changes.rs1use actix_web::{HttpRequest, HttpResponse, web};
29
30use haystack_core::data::{HCol, HDict, HGrid};
31use haystack_core::graph::changelog::DiffOp;
32use haystack_core::kinds::Kind;
33
34use crate::content;
35use crate::error::HaystackError;
36use crate::state::AppState;
37
38pub async fn handle(
50 req: HttpRequest,
51 body: String,
52 state: web::Data<AppState>,
53) -> Result<HttpResponse, HaystackError> {
54 let content_type = req
55 .headers()
56 .get("Content-Type")
57 .and_then(|v| v.to_str().ok())
58 .unwrap_or("");
59 let accept = req
60 .headers()
61 .get("Accept")
62 .and_then(|v| v.to_str().ok())
63 .unwrap_or("");
64
65 let request_grid = content::decode_request_grid(&body, content_type)
66 .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
67
68 let since_version = request_grid
69 .row(0)
70 .and_then(|row| row.get("version"))
71 .and_then(|k| {
72 if let Kind::Number(n) = k {
73 Some(n.val as u64)
74 } else {
75 None
76 }
77 })
78 .unwrap_or(0);
79
80 let current_version = state.graph.version();
81 let diffs = state.graph.changes_since(since_version);
82
83 let mut meta = HDict::new();
84 meta.set(
85 "curVer",
86 Kind::Number(haystack_core::kinds::Number::unitless(
87 current_version as f64,
88 )),
89 );
90
91 if diffs.is_empty() {
92 let grid = HGrid::from_parts(meta, Vec::new(), Vec::new());
93 let (encoded, ct) = content::encode_response_grid(&grid, accept)
94 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
95 return Ok(HttpResponse::Ok().content_type(ct).body(encoded));
96 }
97
98 let cols = vec![
99 HCol::new("version"),
100 HCol::new("op"),
101 HCol::new("ref"),
102 HCol::new("entity"),
103 ];
104
105 let rows: Vec<HDict> = diffs
106 .iter()
107 .map(|diff| {
108 let mut row = HDict::new();
109 row.set(
110 "version",
111 Kind::Number(haystack_core::kinds::Number::unitless(diff.version as f64)),
112 );
113 row.set(
114 "op",
115 Kind::Str(match diff.op {
116 DiffOp::Add => "add".to_string(),
117 DiffOp::Update => "update".to_string(),
118 DiffOp::Remove => "remove".to_string(),
119 }),
120 );
121 row.set("ref", Kind::Str(diff.ref_val.clone()));
122 if let Some(entity) = &diff.new {
124 row.set("entity", Kind::Dict(Box::new(entity.clone())));
125 }
126 row
127 })
128 .collect();
129
130 let grid = HGrid::from_parts(meta, cols, rows);
131 let (encoded, ct) = content::encode_response_grid(&grid, accept)
132 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
133
134 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
135}