things_cloud/
tasks.rs

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    /// An error occurred while communicating with the Things Cloud API
17    #[error("An error occurred while communicating with the Things Cloud API: {0}")]
18    Reqwest(#[from] reqwest::Error),
19
20    /// An error occurred while decoding the API response
21    #[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}