1use crate::graph::{Graph, Node, NodeContents};
2use crate::helper::{get_current_da_time, get_current_time, index_dec_to_ud};
3use crate::{Channel, Result, UrbitAPIError};
4use json::{object, JsonValue};
5
6pub enum Module {
8 Chat,
9 Notebook,
10 Collection,
11 Null,
12}
13
14pub struct GraphStore<'a> {
16 pub channel: &'a mut Channel,
17}
18
19impl<'a> GraphStore<'a> {
20 pub fn new_node(&self, contents: &NodeContents) -> Node {
23 let ship = format!("~{}", self.channel.ship_interface.ship_name);
25 let index = format!("/{}", get_current_da_time());
27
28 let unix_time = get_current_time();
30
31 Node::new(
32 index,
33 ship.clone(),
34 unix_time,
35 vec![],
36 contents.clone(),
37 None,
38 )
39 }
40
41 pub fn new_node_specified(
44 &self,
45 node_index: &str,
46 unix_time: u64,
47 contents: &NodeContents,
48 ) -> Node {
49 let ship = format!("~{}", self.channel.ship_interface.ship_name);
51 Node::new(
52 node_index.to_string(),
53 ship.clone(),
54 unix_time,
55 vec![],
56 contents.clone(),
57 None,
58 )
59 }
60
61 pub fn add_node(
63 &mut self,
64 resource_ship: &str,
65 resource_name: &str,
66 node: &Node,
67 ) -> Result<()> {
68 let prepped_json = object! {
69 "add-nodes": {
70 "resource": {
71 "ship": resource_ship,
72 "name": resource_name
73 },
74 "nodes": node.to_json()
75 }
76 };
77
78 let resp = (&mut self.channel).poke("graph-push-hook", "graph-update-3", &prepped_json)?;
79
80 if resp.status().as_u16() == 204 {
81 Ok(())
82 } else {
83 return Err(UrbitAPIError::FailedToAddNodesToGraphStore(
84 resource_name.to_string(),
85 ));
86 }
87 }
88
89 pub fn add_node_spider(
91 &mut self,
92 resource_ship: &str,
93 resource_name: &str,
94 node: &Node,
95 ) -> Result<()> {
96 let prepped_json = object! {
97 "add-nodes": {
98 "resource": {
99 "ship": resource_ship,
100 "name": resource_name
101 },
102 "nodes": node.to_json()
103 }
104 };
105
106 let resp = self.channel.ship_interface.spider(
107 "graph-update",
108 "graph-view-action",
109 "graph-add-nodes",
110 &prepped_json,
111 )?;
112
113 if resp.status().as_u16() == 200 {
114 Ok(())
115 } else {
116 return Err(UrbitAPIError::FailedToAddNodesToGraphStore(
117 resource_name.to_string(),
118 ));
119 }
120 }
121
122 pub fn remove_nodes(
124 &mut self,
125 resource_ship: &str,
126 resource_name: &str,
127 indices: Vec<&str>,
128 ) -> Result<()> {
129 let prepped_json = object! {
130 "remove-nodes": {
131 "resource": {
132 "ship": resource_ship,
133 "name": resource_name
134 },
135 "indices": indices
136 }
137 };
138
139 let resp = (&mut self.channel).poke("graph-push-hook", "graph-update-3", &prepped_json)?;
140
141 if resp.status().as_u16() == 204 {
142 Ok(())
143 } else {
144 return Err(UrbitAPIError::FailedToRemoveNodesFromGraphStore(
145 resource_name.to_string(),
146 ));
147 }
148 }
149
150 pub fn get_node(
152 &mut self,
153 resource_ship: &str,
154 resource_name: &str,
155 node_index: &str,
156 ) -> Result<Node> {
157 let path_nodes = index_dec_to_ud(node_index);
158 let path = format!("/node/{}/{}{}", resource_ship, resource_name, &path_nodes);
159 let res = self
160 .channel
161 .ship_interface
162 .scry("graph-store", &path, "json")?;
163
164 if res.status().as_u16() == 200 {
166 if let Ok(body) = res.text() {
167 if let Ok(node_json) = json::parse(&body) {
168 return Node::from_graph_update_json(&node_json);
169 }
170 }
171 }
172 Err(UrbitAPIError::FailedToGetGraphNode(format!(
174 "/{}/{}/{}",
175 resource_ship, resource_name, node_index
176 )))
177 }
178
179 pub fn get_node_subset(
182 &mut self,
183 resource_ship: &str,
184 resource_name: &str,
185 node_index: &str,
186 start_index: &str,
187 end_index: &str,
188 ) -> Result<Graph> {
189 let path = format!(
190 "/node-children-subset/{}/{}/{}/{}/{}",
191 resource_ship, resource_name, node_index, end_index, start_index
192 );
193 let res = self
194 .channel
195 .ship_interface
196 .scry("graph-store", &path, "json")?;
197
198 if res.status().as_u16() == 200 {
200 if let Ok(body) = res.text() {
201 if let Ok(graph_json) = json::parse(&body) {
202 return Graph::from_json(graph_json);
203 }
204 }
205 }
206 Err(UrbitAPIError::FailedToGetGraph(resource_name.to_string()))
208 }
209
210 pub fn create_managed_graph(
213 &mut self,
214 graph_resource_name: &str,
215 graph_title: &str,
216 graph_description: &str,
217 graph_module: Module,
218 managed_group_ship: &str,
219 managed_group_name: &str,
220 ) -> Result<()> {
221 let create_req = object! {
222 "create": {
223 "resource": {
224 "ship": format!("~{}", &self.channel.ship_interface.ship_name),
225 "name": graph_resource_name
226 },
227 "title": graph_title,
228 "description": graph_description,
229 "associated": {
230 "group": {
231 "ship": managed_group_ship,
232 "name": managed_group_name,
233 },
234 },
235 "module": module_to_validator_string(&graph_module),
236 "mark": module_to_mark(&graph_module)
237 }
238 };
239
240 let resp = self
241 .channel
242 .ship_interface
243 .spider("graph-view-action", "json", "graph-create", &create_req)
244 .unwrap();
245
246 if resp.status().as_u16() == 200 {
247 Ok(())
248 } else {
249 Err(UrbitAPIError::FailedToCreateGraphInShip(
250 graph_resource_name.to_string(),
251 ))
252 }
253 }
254
255 pub fn create_unmanaged_graph(
258 &mut self,
259 graph_resource_name: &str,
260 graph_title: &str,
261 graph_description: &str,
262 graph_module: Module,
263 ) -> Result<()> {
264 let create_req = object! {
265 "create": {
266 "resource": {
267 "ship": self.channel.ship_interface.ship_name_with_sig(),
268 "name": graph_resource_name
269 },
270 "title": graph_title,
271 "description": graph_description,
272 "associated": {
273 "policy": {
274 "invite": {
275 "pending": []
276 }
277 }
278 },
279 "module": module_to_validator_string(&graph_module),
280 "mark": module_to_mark(&graph_module)
281 }
282 };
283
284 let resp = self
285 .channel
286 .ship_interface
287 .spider("graph-view-action", "json", "graph-create", &create_req)
288 .unwrap();
289
290 if resp.status().as_u16() == 200 {
291 Ok(())
292 } else {
293 Err(UrbitAPIError::FailedToCreateGraphInShip(
294 graph_resource_name.to_string(),
295 ))
296 }
297 }
298
299 pub fn get_graph(&mut self, resource_ship: &str, resource_name: &str) -> Result<Graph> {
330 let path = format!("/graph/{}/{}", resource_ship, resource_name);
331 let res = self
332 .channel
333 .ship_interface
334 .scry("graph-store", &path, "json")?;
335
336 if res.status().as_u16() == 200 {
338 if let Ok(body) = res.text() {
339 if let Ok(graph_json) = json::parse(&body) {
340 return Graph::from_json(graph_json);
341 }
342 }
343 }
344 Err(UrbitAPIError::FailedToGetGraph(resource_name.to_string()))
346 }
347
348 pub fn get_graph_subset(
351 &mut self,
352 resource_ship: &str,
353 resource_name: &str,
354 start_index: &str,
355 end_index: &str,
356 ) -> Result<Graph> {
357 let path = format!(
358 "/graph-subset/{}/{}/{}/{}",
359 resource_ship, resource_name, end_index, start_index
360 );
361 let res = self
362 .channel
363 .ship_interface
364 .scry("graph-store", &path, "json")?;
365
366 if res.status().as_u16() == 200 {
368 if let Ok(body) = res.text() {
369 if let Ok(graph_json) = json::parse(&body) {
370 return Graph::from_json(graph_json);
371 }
372 }
373 }
374 Err(UrbitAPIError::FailedToGetGraph(resource_name.to_string()))
376 }
377
378 pub fn delete_graph(&mut self, resource_ship: &str, resource_name: &str) -> Result<()> {
380 let prepped_json = object! {
381 "delete": {
382 "resource": {
383 "ship": resource_ship,
384 "name": resource_name
385 }
386 }
387 };
388
389 let resp =
390 (&mut self.channel).poke("graph-view-action", "graph-update-3", &prepped_json)?;
391
392 if resp.status().as_u16() == 204 {
393 Ok(())
394 } else {
395 return Err(UrbitAPIError::FailedToRemoveGraphFromGraphStore(
396 resource_name.to_string(),
397 ));
398 }
399 }
400
401 pub fn leave_graph(&mut self, resource_ship: &str, resource_name: &str) -> Result<()> {
403 let prepped_json = object! {
404 "leave": {
405 "resource": {
406 "ship": resource_ship,
407 "name": resource_name
408 }
409 }
410 };
411
412 let resp =
413 (&mut self.channel).poke("graph-view-action", "graph-update-3", &prepped_json)?;
414
415 if resp.status().as_u16() == 204 {
416 Ok(())
417 } else {
418 return Err(UrbitAPIError::FailedToRemoveGraphFromGraphStore(
419 resource_name.to_string(),
420 ));
421 }
422 }
423
424 pub fn archive_graph(&mut self, resource_ship: &str, resource_name: &str) -> Result<String> {
426 let path = format!("/archive/{}/{}", resource_ship, resource_name);
427 let res = self
428 .channel
429 .ship_interface
430 .scry("graph-store", &path, "json")?;
431
432 if res.status().as_u16() == 200 {
433 if let Ok(body) = res.text() {
434 return Ok(body);
435 }
436 }
437 return Err(UrbitAPIError::FailedToArchiveGraph(
438 resource_name.to_string(),
439 ));
440 }
441
442 pub fn unarchive_graph(&mut self, resource_ship: &str, resource_name: &str) -> Result<String> {
444 let path = format!("/unarchive/{}/{}", resource_ship, resource_name);
445 let res = self
446 .channel
447 .ship_interface
448 .scry("graph-store", &path, "json")?;
449
450 if res.status().as_u16() == 200 {
451 if let Ok(body) = res.text() {
452 return Ok(body);
453 }
454 }
455 return Err(UrbitAPIError::FailedToArchiveGraph(
456 resource_name.to_string(),
457 ));
458 }
459
460 pub fn add_tag(&mut self, resource_ship: &str, resource_name: &str, tag: &str) -> Result<()> {
462 let prepped_json = object! {
463 "add-tag": {
464 "resource": {
465 "ship": resource_ship,
466 "name": resource_name
467 },
468 "term": tag
469 }
470 };
471
472 let resp = (&mut self.channel).poke("graph-push-hook", "graph-update-3", &prepped_json)?;
473
474 if resp.status().as_u16() == 204 {
475 Ok(())
476 } else {
477 return Err(UrbitAPIError::FailedToAddTag(resource_name.to_string()));
478 }
479 }
480
481 pub fn remove_tag(
483 &mut self,
484 resource_ship: &str,
485 resource_name: &str,
486 tag: &str,
487 ) -> Result<()> {
488 let prepped_json = object! {
489 "remove-tag": {
490 "resource": {
491 "ship": resource_ship,
492 "name": resource_name
493 },
494 "term": tag
495 }
496 };
497
498 let resp = (&mut self.channel).poke("graph-push-hook", "graph-update-3", &prepped_json)?;
499
500 if resp.status().as_u16() == 204 {
501 Ok(())
502 } else {
503 return Err(UrbitAPIError::FailedToRemoveTag(resource_name.to_string()));
504 }
505 }
506
507 pub fn get_keys(&mut self) -> Result<Vec<JsonValue>> {
509 let resp = self
510 .channel
511 .ship_interface
512 .scry("graph-store", "/keys", "json")?;
513
514 if resp.status().as_u16() == 200 {
515 let json_text = resp.text()?;
516 if let Ok(json) = json::parse(&json_text) {
517 let keys = json["graph-update"]["keys"].clone();
518 let mut keys_list = vec![];
519 for key in keys.members() {
520 keys_list.push(key.clone())
521 }
522 return Ok(keys_list);
523 }
524 }
525 return Err(UrbitAPIError::FailedToFetchKeys);
526 }
527
528 pub fn get_tags(&mut self) -> Result<Vec<JsonValue>> {
530 let resp = self
531 .channel
532 .ship_interface
533 .scry("graph-store", "/tags", "json")?;
534
535 if resp.status().as_u16() == 200 {
536 let json_text = resp.text()?;
537 if let Ok(json) = json::parse(&json_text) {
538 let tags = json["graph-update"]["tags"].clone();
539 let mut tags_list = vec![];
540 for tag in tags.members() {
541 tags_list.push(tag.clone())
542 }
543 return Ok(tags_list);
544 }
545 }
546 return Err(UrbitAPIError::FailedToFetchTags);
547 }
548
549 pub fn get_tag_queries(&mut self) -> Result<Vec<JsonValue>> {
551 let resp = self
552 .channel
553 .ship_interface
554 .scry("graph-store", "/tag-queries", "json")?;
555
556 if resp.status().as_u16() == 200 {
557 let json_text = resp.text()?;
558 if let Ok(json) = json::parse(&json_text) {
559 let tags = json["graph-update"]["tag-queries"].clone();
560 let mut tags_list = vec![];
561 for tag in tags.members() {
562 tags_list.push(tag.clone())
563 }
564 return Ok(tags_list);
565 }
566 }
567 return Err(UrbitAPIError::FailedToFetchTags);
568 }
569
570 pub fn peek_update_log(&mut self, resource_ship: &str, resource_name: &str) -> Result<String> {
572 let path = format!("/peek-update-log/{}/{}", resource_ship, resource_name);
573 let res = self
574 .channel
575 .ship_interface
576 .scry("graph-store", &path, "json")?;
577
578 if res.status().as_u16() == 200 {
580 if let Ok(body) = res.text() {
581 return Ok(body);
582 }
583 }
584 Err(UrbitAPIError::FailedToGetGraph(resource_name.to_string()))
586 }
587
588 pub fn get_update_log(&mut self, resource_ship: &str, resource_name: &str) -> Result<String> {
590 let path = format!("/update-log/{}/{}", resource_ship, resource_name);
591 let res = self
592 .channel
593 .ship_interface
594 .scry("graph-store", &path, "json")?;
595
596 if res.status().as_u16() == 200 {
598 if let Ok(body) = res.text() {
599 return Ok(body);
600 }
601 }
602 Err(UrbitAPIError::FailedToGetGraph(resource_name.to_string()))
604 }
605
606 pub fn get_update_log_subset(
608 &mut self,
609 resource_ship: &str,
610 resource_name: &str,
611 start_index: &str,
612 end_index: &str,
613 ) -> Result<String> {
614 let path = format!(
615 "/update-log-subset/{}/{}/{}/{}",
616 resource_ship, resource_name, end_index, start_index
617 );
618 let res = self
619 .channel
620 .ship_interface
621 .scry("graph-store", &path, "json")?;
622
623 if res.status().as_u16() == 200 {
625 if let Ok(body) = res.text() {
626 return Ok(body);
627 }
628 }
629 Err(UrbitAPIError::FailedToGetUpdateLog(
631 resource_name.to_string(),
632 ))
633 }
634}
635
636pub fn module_to_validator_string(module: &Module) -> String {
637 match module {
638 Module::Chat => "graph-validator-chat".to_string(),
639 Module::Notebook => "graph-validator-publish".to_string(),
640 Module::Collection => "graph-validator-link".to_string(),
641 Module::Null => "".to_string(),
642 }
643}
644
645pub fn module_to_mark(module: &Module) -> String {
646 match module {
647 Module::Chat => "chat".to_string(),
648 Module::Notebook => "publish".to_string(),
649 Module::Collection => "link".to_string(),
650 Module::Null => "".to_string(),
651 }
652}