+121 -114 +/-3 browse
1 | diff --git a/core/src/connection.rs b/core/src/connection.rs |
2 | index bc3a1d8..6aabd6f 100644 |
3 | --- a/core/src/connection.rs |
4 | +++ b/core/src/connection.rs |
5 | @@ -32,6 +32,7 @@ use crate::{ |
6 | config::Configuration, |
7 | errors::{ErrorKind::*, *}, |
8 | models::{changesets::MailingListChangeset, DbVal, ListOwner, MailingList, Post}, |
9 | + StripCarets, |
10 | }; |
11 | |
12 | /// A connection to a `mailpot` database. |
13 | @@ -592,6 +593,111 @@ impl Connection { |
14 | Ok(ret) |
15 | } |
16 | |
17 | + /// Fetch the contents of a single thread in the form of `(depth, post)` |
18 | + /// where `depth` is the reply distance between a message and the thread |
19 | + /// root message. |
20 | + pub fn list_thread(&self, list_pk: i64, root: &str) -> Result<Vec<(i64, DbVal<Post>)>> { |
21 | + let mut stmt = self |
22 | + .connection |
23 | + .prepare( |
24 | + "WITH RECURSIVE cte_replies AS MATERIALIZED |
25 | + ( |
26 | + SELECT |
27 | + pk, |
28 | + message_id, |
29 | + REPLACE( |
30 | + TRIM( |
31 | + SUBSTR( |
32 | + CAST(message AS TEXT), |
33 | + INSTR( |
34 | + CAST(message AS TEXT), |
35 | + 'In-Reply-To: ' |
36 | + ) |
37 | + + |
38 | + LENGTH('in-reply-to: '), |
39 | + INSTR( |
40 | + SUBSTR( |
41 | + CAST(message AS TEXT), |
42 | + INSTR( |
43 | + CAST(message AS TEXT), |
44 | + 'In-Reply-To: ') |
45 | + + |
46 | + LENGTH('in-reply-to: ') |
47 | + ), |
48 | + '>' |
49 | + ) |
50 | + ) |
51 | + ), |
52 | + ' ', |
53 | + '' |
54 | + ) AS in_reply_to, |
55 | + INSTR( |
56 | + CAST(message AS TEXT), |
57 | + 'In-Reply-To: ' |
58 | + ) AS offset |
59 | + FROM post |
60 | + WHERE |
61 | + offset > 0 |
62 | + UNION |
63 | + SELECT |
64 | + pk, |
65 | + message_id, |
66 | + NULL AS in_reply_to, |
67 | + INSTR( |
68 | + CAST(message AS TEXT), |
69 | + 'In-Reply-To: ' |
70 | + ) AS offset |
71 | + FROM post |
72 | + WHERE |
73 | + offset = 0 |
74 | + ), |
75 | + cte_thread(parent, root, depth) AS ( |
76 | + SELECT DISTINCT |
77 | + message_id AS parent, |
78 | + message_id AS root, |
79 | + 0 AS depth |
80 | + FROM cte_replies |
81 | + WHERE |
82 | + in_reply_to IS NULL |
83 | + UNION ALL |
84 | + SELECT |
85 | + t.message_id AS parent, |
86 | + cte_thread.root AS root, |
87 | + (cte_thread.depth + 1) AS depth |
88 | + FROM cte_replies |
89 | + AS t |
90 | + JOIN |
91 | + cte_thread |
92 | + ON cte_thread.parent = t.in_reply_to |
93 | + WHERE t.in_reply_to IS NOT NULL |
94 | + ) |
95 | + SELECT * FROM cte_thread WHERE root = ? ORDER BY root, depth;", |
96 | + ) |
97 | + .unwrap(); |
98 | + let iter = stmt |
99 | + .query_map(rusqlite::params![root], |row| { |
100 | + let parent: String = row.get("parent")?; |
101 | + let root: String = row.get("root")?; |
102 | + let depth: i64 = row.get("depth")?; |
103 | + Ok((parent, root, depth)) |
104 | + })?; |
105 | + let mut ret = vec![]; |
106 | + for post in iter { |
107 | + ret.push(post?); |
108 | + } |
109 | + let posts = self.list_posts(list_pk, None).unwrap(); |
110 | + let ret = ret.into_iter() |
111 | + .filter_map(|(m, _, depth)| { |
112 | + posts |
113 | + .iter() |
114 | + .find(|p| m.as_str().strip_carets() == p.message_id.as_str().strip_carets()) |
115 | + .map(|p| (depth, p.clone())) |
116 | + }) |
117 | + .skip(1) |
118 | + .collect(); |
119 | + Ok(ret) |
120 | + } |
121 | + |
122 | /// Fetch the owners of a mailing list. |
123 | pub fn list_owners(&self, pk: i64) -> Result<Vec<DbVal<ListOwner>>> { |
124 | let mut stmt = self |
125 | diff --git a/web/src/lists.rs b/web/src/lists.rs |
126 | index 6499e10..ef99149 100644 |
127 | --- a/web/src/lists.rs |
128 | +++ b/web/src/lists.rs |
129 | @@ -19,6 +19,7 @@ |
130 | |
131 | use chrono::TimeZone; |
132 | use indexmap::IndexMap; |
133 | + use mailpot::models::Post; |
134 | |
135 | use super::*; |
136 | |
137 | @@ -172,7 +173,20 @@ pub async fn list_post( |
138 | StatusCode::NOT_FOUND, |
139 | )); |
140 | }; |
141 | - let thread = super::utils::thread_db(&db, list.pk, &post.message_id); |
142 | + let thread: Vec<(i64, DbVal<Post>, String, String)> = { |
143 | + let thread: Vec<(i64, DbVal<Post>)> = db.list_thread(list.pk, &post.message_id)?; |
144 | + |
145 | + thread |
146 | + .into_iter() |
147 | + .map(|(depth, p)| { |
148 | + let envelope = melib::Envelope::from_bytes(p.message.as_slice(), None).unwrap(); |
149 | + let body = envelope.body_bytes(p.message.as_slice()); |
150 | + let body_text = body.text(); |
151 | + let date = envelope.date_as_str().to_string(); |
152 | + (depth, p, body_text, date) |
153 | + }) |
154 | + .collect() |
155 | + }; |
156 | let envelope = melib::Envelope::from_bytes(post.message.as_slice(), None) |
157 | .with_status(StatusCode::BAD_REQUEST)?; |
158 | let body = envelope.body_bytes(post.message.as_slice()); |
159 | diff --git a/web/src/utils.rs b/web/src/utils.rs |
160 | index 96af462..3d2c35e 100644 |
161 | --- a/web/src/utils.rs |
162 | +++ b/web/src/utils.rs |
163 | @@ -301,119 +301,6 @@ pub struct ThreadEntry { |
164 | pub datetime: String, |
165 | } |
166 | |
167 | - pub fn thread_db( |
168 | - db: &mailpot::Connection, |
169 | - list: i64, |
170 | - root: &str, |
171 | - ) -> Vec<(i64, DbVal<mailpot::models::Post>, String, String)> { |
172 | - let mut stmt = db |
173 | - .connection |
174 | - .prepare( |
175 | - "WITH RECURSIVE cte_replies AS MATERIALIZED |
176 | - ( |
177 | - SELECT |
178 | - pk, |
179 | - message_id, |
180 | - REPLACE( |
181 | - TRIM( |
182 | - SUBSTR( |
183 | - CAST(message AS TEXT), |
184 | - INSTR( |
185 | - CAST(message AS TEXT), |
186 | - 'In-Reply-To: ' |
187 | - ) |
188 | - + |
189 | - LENGTH('in-reply-to: '), |
190 | - INSTR( |
191 | - SUBSTR( |
192 | - CAST(message AS TEXT), |
193 | - INSTR( |
194 | - CAST(message AS TEXT), |
195 | - 'In-Reply-To: ') |
196 | - + |
197 | - LENGTH('in-reply-to: ') |
198 | - ), |
199 | - '>' |
200 | - ) |
201 | - ) |
202 | - ), |
203 | - ' ', |
204 | - '' |
205 | - ) AS in_reply_to, |
206 | - INSTR( |
207 | - CAST(message AS TEXT), |
208 | - 'In-Reply-To: ' |
209 | - ) AS offset |
210 | - FROM post |
211 | - WHERE |
212 | - offset > 0 |
213 | - UNION |
214 | - SELECT |
215 | - pk, |
216 | - message_id, |
217 | - NULL AS in_reply_to, |
218 | - INSTR( |
219 | - CAST(message AS TEXT), |
220 | - 'In-Reply-To: ' |
221 | - ) AS offset |
222 | - FROM post |
223 | - WHERE |
224 | - offset = 0 |
225 | - ), |
226 | - cte_thread(parent, root, depth) AS ( |
227 | - SELECT DISTINCT |
228 | - message_id AS parent, |
229 | - message_id AS root, |
230 | - 0 AS depth |
231 | - FROM cte_replies |
232 | - WHERE |
233 | - in_reply_to IS NULL |
234 | - UNION ALL |
235 | - SELECT |
236 | - t.message_id AS parent, |
237 | - cte_thread.root AS root, |
238 | - (cte_thread.depth + 1) AS depth |
239 | - FROM cte_replies |
240 | - AS t |
241 | - JOIN |
242 | - cte_thread |
243 | - ON cte_thread.parent = t.in_reply_to |
244 | - WHERE t.in_reply_to IS NOT NULL |
245 | - ) |
246 | - SELECT * FROM cte_thread WHERE root = ? ORDER BY root, depth;", |
247 | - ) |
248 | - .unwrap(); |
249 | - let iter = stmt |
250 | - .query_map(rusqlite::params![root], |row| { |
251 | - let parent: String = row.get("parent")?; |
252 | - let root: String = row.get("root")?; |
253 | - let depth: i64 = row.get("depth")?; |
254 | - Ok((parent, root, depth)) |
255 | - }) |
256 | - .unwrap(); |
257 | - let mut ret = vec![]; |
258 | - for post in iter { |
259 | - let post = post.unwrap(); |
260 | - ret.push(post); |
261 | - } |
262 | - let posts = db.list_posts(list, None).unwrap(); |
263 | - ret.into_iter() |
264 | - .filter_map(|(m, _, depth)| { |
265 | - posts |
266 | - .iter() |
267 | - .find(|p| m.as_str().strip_carets() == p.message_id.as_str().strip_carets()) |
268 | - .map(|p| { |
269 | - let envelope = melib::Envelope::from_bytes(p.message.as_slice(), None).unwrap(); |
270 | - let body = envelope.body_bytes(p.message.as_slice()); |
271 | - let body_text = body.text(); |
272 | - let date = envelope.date_as_str().to_string(); |
273 | - (depth, p.clone(), body_text, date) |
274 | - }) |
275 | - }) |
276 | - .skip(1) |
277 | - .collect() |
278 | - } |
279 | - |
280 | pub fn thread( |
281 | envelopes: &Arc<std::sync::RwLock<HashMap<melib::EnvelopeHash, melib::Envelope>>>, |
282 | threads: &melib::Threads, |