kanbrain_core/api/
json.rs1use crate::brain::Brain;
5use crate::model::{Asked, Author, Comment, Kind, Task, fmt_ts, now};
6use serde::Serialize;
7
8#[derive(Debug, Serialize)]
10pub struct Entry<'a> {
11 pub id: u32,
13 pub kind: &'static str,
15 pub title: &'a str,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub status: Option<&'static str>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub decision: Option<&'static str>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub asked: Option<&'static str>,
26 pub tags: &'a [String],
28 #[serde(skip_serializing_if = "<[u32]>::is_empty")]
30 pub links: &'a [u32],
31 #[serde(skip_serializing_if = "Vec::is_empty")]
33 pub repo_links: Vec<String>,
34 #[serde(skip_serializing_if = "Vec::is_empty")]
36 pub linked_from: Vec<u32>,
37 pub created: String,
39 #[serde(skip_serializing_if = "str::is_empty")]
41 pub description: &'a str,
42 #[serde(skip_serializing_if = "Vec::is_empty")]
44 pub comments: Vec<EntryComment<'a>>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub tracked_minutes: Option<i64>,
48 #[serde(skip_serializing_if = "std::ops::Not::not")]
50 pub running: bool,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub path: Option<String>,
54}
55
56#[derive(Debug, Serialize)]
58pub struct EntryComment<'a> {
59 pub created: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub edited: Option<String>,
64 pub body: &'a str,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub author: Option<&'static str>,
69 #[serde(skip_serializing_if = "std::ops::Not::not")]
71 pub question: bool,
72}
73
74#[derive(Debug, Serialize)]
76pub struct Summary<'a> {
77 pub id: u32,
79 pub kind: &'static str,
81 pub title: &'a str,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub status: Option<&'static str>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub decision: Option<&'static str>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub asked: Option<&'static str>,
92 pub tags: &'a [String],
94}
95
96impl<'a> Entry<'a> {
97 pub fn new(brain: &Brain, task: &'a Task) -> Self {
99 let now = now();
100 Self {
101 id: task.id,
102 kind: task.kind.as_str(),
103 title: &task.title,
104 status: task.is_task().then(|| task.status.as_str()),
105 decision: decision(task),
106 asked: asked(task),
107 tags: &task.tags,
108 links: &task.links,
109 repo_links: task.repo_links.iter().map(ToString::to_string).collect(),
110 linked_from: brain.backlinks(task.id).iter().map(|t| t.id).collect(),
111 created: fmt_ts(task.created),
112 description: &task.description,
113 comments: task.comments.iter().map(EntryComment::new).collect(),
114 tracked_minutes: (!task.sessions.is_empty()).then(|| task.total(now).num_minutes()),
115 running: task.is_running(),
116 path: task.path.as_ref().map(|p| p.display().to_string()),
117 }
118 }
119}
120
121impl<'a> EntryComment<'a> {
122 fn new(comment: &'a Comment) -> Self {
124 Self {
125 created: fmt_ts(comment.created),
126 edited: comment.edited.map(fmt_ts),
127 body: &comment.body,
128 author: (comment.author != Author::Human).then(|| comment.author.as_str()),
129 question: comment.question,
130 }
131 }
132}
133
134impl<'a> Summary<'a> {
135 pub fn new(task: &'a Task) -> Self {
137 Self {
138 id: task.id,
139 kind: task.kind.as_str(),
140 title: &task.title,
141 status: task.is_task().then(|| task.status.as_str()),
142 decision: decision(task),
143 asked: asked(task),
144 tags: &task.tags,
145 }
146 }
147}
148
149fn decision(task: &Task) -> Option<&'static str> {
151 (task.kind == Kind::Decision).then(|| task.decision.as_str())
152}
153
154fn asked(task: &Task) -> Option<&'static str> {
156 task.asked().map(|asked| match asked {
157 Asked::Waiting(_) => "waiting for an answer",
158 Asked::Answered(_) => "answered",
159 })
160}