1use crate::elements::{Element, ElementKind};
4use crate::ref_resolver::RefResolver;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ElementJson {
12 #[serde(rename = "ref")]
14 pub epoch_ref: String,
15 pub human_ref: Option<String>,
17 pub date: String,
19 #[serde(rename = "type")]
21 pub element_type: String,
22 pub tags: Vec<String>,
23 pub body: String,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub status: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub at: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub start: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub end: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub name: Option<String>,
35}
36
37#[derive(Debug, Clone, Serialize)]
39pub struct StatsJson {
40 pub dates: Vec<String>,
41 pub tasks: TaskStats,
42 pub notes: usize,
43 pub logs: LogStats,
44 pub reminders: usize,
45 pub characters: usize,
46}
47
48#[derive(Debug, Clone, Serialize)]
49pub struct TaskStats {
50 pub total: usize,
51 pub open: usize,
52 pub done: usize,
53}
54
55#[derive(Debug, Clone, Serialize)]
56pub struct LogStats {
57 pub total: usize,
58 pub duration_minutes: i64,
59}
60
61#[derive(Debug, Clone, Serialize)]
63pub struct ErrorResponse {
64 pub error: String,
65}
66
67#[derive(Debug, Clone, Deserialize)]
71pub struct AppendRequest {
72 #[serde(rename = "type")]
73 pub element_type: String,
74 pub body: String,
75 #[serde(default)]
76 pub tags: Vec<String>,
77 pub status: Option<String>,
78 pub at: Option<String>,
79 pub start: Option<String>,
80 pub end: Option<String>,
81 pub name: Option<String>,
82 pub date: Option<String>,
84}
85
86#[derive(Debug, Clone, Deserialize)]
88pub struct UpdateRequest {
89 pub status: Option<String>,
90 pub at: Option<String>,
91 pub start: Option<String>,
92 pub end: Option<String>,
93 pub name: Option<String>,
94 pub body: Option<String>,
96 pub date: Option<String>,
98}
99
100pub fn element_to_json(
104 el: &Element,
105 epoch_ref: &str,
106 date_str: &str,
107 resolver: &RefResolver,
108) -> ElementJson {
109 let human_ref = resolver.to_human(epoch_ref).map(|s| s.to_string());
110 let date = format_date_str(date_str);
111
112 match el {
113 Element::Task { data, body_str, .. } => ElementJson {
114 epoch_ref: epoch_ref.to_string(),
115 human_ref,
116 date,
117 element_type: "task".into(),
118 tags: data.tags.clone(),
119 body: body_str.trim().to_string(),
120 status: Some(data.status_str().to_string()),
121 at: None,
122 start: None,
123 end: None,
124 name: None,
125 },
126 Element::Note { data, body_str, .. } => ElementJson {
127 epoch_ref: epoch_ref.to_string(),
128 human_ref,
129 date,
130 element_type: "note".into(),
131 tags: data.tags.clone(),
132 body: body_str.trim().to_string(),
133 status: None,
134 at: None,
135 start: None,
136 end: None,
137 name: None,
138 },
139 Element::Log { data, body_str, .. } => ElementJson {
140 epoch_ref: epoch_ref.to_string(),
141 human_ref,
142 date,
143 element_type: "log".into(),
144 tags: data.tags.clone(),
145 body: body_str.trim().to_string(),
146 status: None,
147 at: None,
148 start: data.start.clone(),
149 end: data.end.clone(),
150 name: None,
151 },
152 Element::Reminder { data, body_str, .. } => ElementJson {
153 epoch_ref: epoch_ref.to_string(),
154 human_ref,
155 date,
156 element_type: "reminder".into(),
157 tags: data.tags.clone(),
158 body: body_str.trim().to_string(),
159 status: None,
160 at: data.at.clone(),
161 start: None,
162 end: None,
163 name: None,
164 },
165 Element::Character { data, body_str, .. } => ElementJson {
166 epoch_ref: epoch_ref.to_string(),
167 human_ref,
168 date,
169 element_type: "character".into(),
170 tags: data.tags.clone(),
171 body: body_str.trim().to_string(),
172 status: None,
173 at: None,
174 start: None,
175 end: None,
176 name: data.name.clone(),
177 },
178 Element::MpsGroup { body_str, .. } => ElementJson {
179 epoch_ref: epoch_ref.to_string(),
180 human_ref,
181 date,
182 element_type: "mps".into(),
183 tags: vec![],
184 body: body_str.trim().to_string(),
185 status: None,
186 at: None,
187 start: None,
188 end: None,
189 name: None,
190 },
191 Element::Unknown { sign, body_str, .. } => ElementJson {
192 epoch_ref: epoch_ref.to_string(),
193 human_ref,
194 date,
195 element_type: sign.clone(),
196 tags: vec![],
197 body: body_str.trim().to_string(),
198 status: None,
199 at: None,
200 start: None,
201 end: None,
202 name: None,
203 },
204 }
205}
206
207pub fn format_date_str(date_str: &str) -> String {
209 if date_str.len() >= 8 {
210 format!(
211 "{}-{}-{}",
212 &date_str[..4],
213 &date_str[4..6],
214 &date_str[6..8]
215 )
216 } else {
217 date_str.to_string()
218 }
219}
220
221pub fn compute_stats(
223 date_elements: Vec<(String, indexmap::IndexMap<String, Element>)>,
224) -> StatsJson {
225 let mut dates = Vec::new();
226 let mut task_total = 0usize;
227 let mut task_open = 0usize;
228 let mut task_done = 0usize;
229 let mut notes = 0usize;
230 let mut log_total = 0usize;
231 let mut log_duration = 0i64;
232 let mut reminders = 0usize;
233 let mut characters = 0usize;
234
235 for (date_str, els) in date_elements {
236 dates.push(format_date_str(&date_str));
237 for (_, el) in &els {
238 match el.kind() {
239 ElementKind::Task => {
240 task_total += 1;
241 if let Element::Task { data, .. } = el {
242 if data.is_done() {
243 task_done += 1;
244 } else {
245 task_open += 1;
246 }
247 }
248 }
249 ElementKind::Note => notes += 1,
250 ElementKind::Log => {
251 log_total += 1;
252 if let Element::Log { data, .. } = el {
253 if let Some(mins) = data.duration_minutes() {
254 log_duration += mins as i64;
255 }
256 }
257 }
258 ElementKind::Reminder => reminders += 1,
259 ElementKind::Character => characters += 1,
260 ElementKind::MpsGroup | ElementKind::Unknown => {}
261 }
262 }
263 }
264
265 dates.sort();
266 dates.dedup();
267
268 StatsJson {
269 dates,
270 tasks: TaskStats {
271 total: task_total,
272 open: task_open,
273 done: task_done,
274 },
275 notes,
276 logs: LogStats {
277 total: log_total,
278 duration_minutes: log_duration,
279 },
280 reminders,
281 characters,
282 }
283}