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 date: Option<String>,
96}
97
98pub fn element_to_json(
102 el: &Element,
103 epoch_ref: &str,
104 date_str: &str,
105 resolver: &RefResolver,
106) -> ElementJson {
107 let human_ref = resolver.to_human(epoch_ref).map(|s| s.to_string());
108 let date = format_date_str(date_str);
109
110 match el {
111 Element::Task { data, body_str, .. } => ElementJson {
112 epoch_ref: epoch_ref.to_string(),
113 human_ref,
114 date,
115 element_type: "task".into(),
116 tags: data.tags.clone(),
117 body: body_str.trim().to_string(),
118 status: Some(data.status_str().to_string()),
119 at: None,
120 start: None,
121 end: None,
122 name: None,
123 },
124 Element::Note { data, body_str, .. } => ElementJson {
125 epoch_ref: epoch_ref.to_string(),
126 human_ref,
127 date,
128 element_type: "note".into(),
129 tags: data.tags.clone(),
130 body: body_str.trim().to_string(),
131 status: None,
132 at: None,
133 start: None,
134 end: None,
135 name: None,
136 },
137 Element::Log { data, body_str, .. } => ElementJson {
138 epoch_ref: epoch_ref.to_string(),
139 human_ref,
140 date,
141 element_type: "log".into(),
142 tags: data.tags.clone(),
143 body: body_str.trim().to_string(),
144 status: None,
145 at: None,
146 start: data.start.clone(),
147 end: data.end.clone(),
148 name: None,
149 },
150 Element::Reminder { data, body_str, .. } => ElementJson {
151 epoch_ref: epoch_ref.to_string(),
152 human_ref,
153 date,
154 element_type: "reminder".into(),
155 tags: data.tags.clone(),
156 body: body_str.trim().to_string(),
157 status: None,
158 at: data.at.clone(),
159 start: None,
160 end: None,
161 name: None,
162 },
163 Element::Character { data, body_str, .. } => ElementJson {
164 epoch_ref: epoch_ref.to_string(),
165 human_ref,
166 date,
167 element_type: "character".into(),
168 tags: data.tags.clone(),
169 body: body_str.trim().to_string(),
170 status: None,
171 at: None,
172 start: None,
173 end: None,
174 name: data.name.clone(),
175 },
176 Element::MpsGroup { body_str, .. } => ElementJson {
177 epoch_ref: epoch_ref.to_string(),
178 human_ref,
179 date,
180 element_type: "mps".into(),
181 tags: vec![],
182 body: body_str.trim().to_string(),
183 status: None,
184 at: None,
185 start: None,
186 end: None,
187 name: None,
188 },
189 Element::Unknown { sign, body_str, .. } => ElementJson {
190 epoch_ref: epoch_ref.to_string(),
191 human_ref,
192 date,
193 element_type: sign.clone(),
194 tags: vec![],
195 body: body_str.trim().to_string(),
196 status: None,
197 at: None,
198 start: None,
199 end: None,
200 name: None,
201 },
202 }
203}
204
205pub fn format_date_str(date_str: &str) -> String {
207 if date_str.len() >= 8 {
208 format!(
209 "{}-{}-{}",
210 &date_str[..4],
211 &date_str[4..6],
212 &date_str[6..8]
213 )
214 } else {
215 date_str.to_string()
216 }
217}
218
219pub fn compute_stats(
221 date_elements: Vec<(String, indexmap::IndexMap<String, Element>)>,
222) -> StatsJson {
223 let mut dates = Vec::new();
224 let mut task_total = 0usize;
225 let mut task_open = 0usize;
226 let mut task_done = 0usize;
227 let mut notes = 0usize;
228 let mut log_total = 0usize;
229 let mut log_duration = 0i64;
230 let mut reminders = 0usize;
231 let mut characters = 0usize;
232
233 for (date_str, els) in date_elements {
234 dates.push(format_date_str(&date_str));
235 for (_, el) in &els {
236 match el.kind() {
237 ElementKind::Task => {
238 task_total += 1;
239 if let Element::Task { data, .. } = el {
240 if data.is_done() {
241 task_done += 1;
242 } else {
243 task_open += 1;
244 }
245 }
246 }
247 ElementKind::Note => notes += 1,
248 ElementKind::Log => {
249 log_total += 1;
250 if let Element::Log { data, .. } = el {
251 if let Some(mins) = data.duration_minutes() {
252 log_duration += mins as i64;
253 }
254 }
255 }
256 ElementKind::Reminder => reminders += 1,
257 ElementKind::Character => characters += 1,
258 ElementKind::MpsGroup | ElementKind::Unknown => {}
259 }
260 }
261 }
262
263 dates.sort();
264 dates.dedup();
265
266 StatsJson {
267 dates,
268 tasks: TaskStats {
269 total: task_total,
270 open: task_open,
271 done: task_done,
272 },
273 notes,
274 logs: LogStats {
275 total: log_total,
276 duration_minutes: log_duration,
277 },
278 reminders,
279 characters,
280 }
281}