1use async_recursion::async_recursion;
2use serde_json::{Map, Value};
3use std::collections::HashMap;
4
5use crate::Account;
6
7#[derive(Debug)]
8pub struct History<'a> {
9 pub tasks: Vec<Value>,
10 last_index: usize,
11 account: &'a Account,
12}
13
14#[derive(Debug, thiserror::Error)]
15pub enum Error {
16 #[error("An error occurred while communicating with the Things Cloud API: {0}")]
18 Reqwest(#[from] reqwest::Error),
19
20 #[error("An error occurred while decoding the response: {0}")]
22 Decode(#[from] serde_path_to_error::Error<serde_json::Error>),
23}
24
25impl<'a> History<'a> {
26 pub(crate) async fn from_account(account: &'a Account) -> Result<Self, Error> {
27 let history = Self::fetch_history(account, 0).await?;
28 let tasks_history = history
29 .items
30 .into_iter()
31 .map(|c| {
32 c.into_iter()
33 .filter(|(_, item)| item.kind.starts_with("Task"))
34 })
35 .fold(HashMap::<String, Vec<_>>::new(), |mut acc, map| {
36 for (key, value) in map {
37 acc.entry(key).or_default().push(value);
38 }
39 acc
40 });
41
42 let tasks = tasks_history
43 .into_iter()
44 .filter(|(_, task_history)| {
45 !task_history
46 .iter()
47 .any(|item| item.action == ItemAction::Deleted)
48 })
49 .map(|(_, task_history)| {
50 task_history
51 .into_iter()
52 .fold(Map::default(), |mut acc, mut item| {
53 let task = std::mem::take(item.payload.as_object_mut().unwrap());
54 acc.extend(task);
55 acc
56 })
57 })
58 .map(Value::Object)
59 .collect::<Vec<_>>();
60
61 Ok({
62 Self {
63 tasks,
64 account,
65 last_index: history.last_index,
66 }
67 })
68 }
69
70 #[must_use]
71 pub const fn from_index(account: &'a Account, index: usize) -> Self {
72 Self {
73 account,
74 tasks: vec![],
75 last_index: index,
76 }
77 }
78
79 #[async_recursion]
80 async fn fetch_history(account: &Account, index: usize) -> Result<HistoryApiResponse, Error> {
81 let request = account
82 .client
83 .get(format!(
84 "https://cloud.culturedcode.com/version/1/history/{}/items?start-index={index}",
85 account.history_key
86 ))
87 .send()
88 .await?
89 .bytes()
90 .await?;
91
92 let mut history = serde_path_to_error::deserialize::<_, HistoryApiResponse>(
93 &mut serde_json::Deserializer::from_slice(&request),
94 )?;
95
96 if index + history.items.len() < history.last_index {
97 let rec_history = Self::fetch_history(account, index + history.items.len()).await?;
98 history.items.extend(rec_history.items);
99 }
100
101 Ok(history)
102 }
103}
104
105#[derive(Debug, serde::Deserialize)]
106struct HistoryApiResponse {
107 #[serde(rename = "current-item-index")]
108 last_index: usize,
109 items: Vec<HashMap<String, HistoryItem>>,
110}
111
112#[derive(Debug, serde::Deserialize)]
113struct HistoryItem {
114 #[serde(rename = "e")]
115 kind: String,
116 #[serde(rename = "t")]
117 action: ItemAction,
118 #[serde(rename = "p")]
119 payload: serde_json::Value,
120}
121
122#[repr(u8)]
123#[derive(Debug, Clone, Copy, serde_repr::Deserialize_repr, PartialEq, Eq)]
124enum ItemAction {
125 Create,
126 Modified,
127 Deleted,
128}
129
130#[repr(u8)]
131#[derive(Debug, Clone, Copy, serde_repr::Deserialize_repr, PartialEq, Eq)]
132pub enum TaskStatus {
133 Pending = 0,
134 Completed = 3,
135 Cancelled = 2,
136}