Commit
+84 -35 +/-9 browse
1 | diff --git a/ayllu-mail/src/delivery.rs b/ayllu-mail/src/delivery.rs |
2 | index 5a58aba..a9e2f70 100644 |
3 | --- a/ayllu-mail/src/delivery.rs |
4 | +++ b/ayllu-mail/src/delivery.rs |
5 | @@ -1,6 +1,6 @@ |
6 | - use std::collections::HashMap; |
7 | + use std::{collections::HashMap, fmt::Display}; |
8 | |
9 | - use mail_parser::{Address, MessageParser}; |
10 | + use mail_parser::{Address, HeaderValue, Message, MessageParser}; |
11 | use maildir::Maildir; |
12 | |
13 | use ayllu_database::mail::MailExt; |
14 | @@ -25,6 +25,40 @@ fn addresses(address: Option<&Address<'_>>) -> Vec<String> { |
15 | } |
16 | } |
17 | |
18 | + /// Return all references and in-reply-to |
19 | + pub struct References(Option<String>, Vec<String>); |
20 | + |
21 | + impl From<Message<'_>> for References { |
22 | + fn from(value: Message<'_>) -> Self { |
23 | + let references = match value.references() { |
24 | + HeaderValue::Text(cow) => vec![cow.to_string()], |
25 | + HeaderValue::TextList(vec) => vec.iter().map(|text| text.to_string()).collect(), |
26 | + _ => vec![], |
27 | + }; |
28 | + References( |
29 | + value |
30 | + .in_reply_to() |
31 | + .clone() |
32 | + .into_text() |
33 | + .map(|text| text.to_string()), |
34 | + references, |
35 | + ) |
36 | + } |
37 | + } |
38 | + |
39 | + impl Display for References { |
40 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
41 | + write!(f, "In Reply To: {:?}", self.0)?; |
42 | + for (i, reference) in self.1.iter().enumerate() { |
43 | + if i == 0 { |
44 | + writeln!(f, "\nReferences:")?; |
45 | + } |
46 | + writeln!(f, "\t{}", reference)?; |
47 | + } |
48 | + Ok(()) |
49 | + } |
50 | + } |
51 | + |
52 | #[derive(Clone, Default)] |
53 | pub struct Summary { |
54 | pub successful: Vec<String>, |
55 | @@ -57,7 +91,14 @@ pub async fn deliver_all(config: &Config) -> Result<Summary, Error> { |
56 | .parse(&message_bytes) |
57 | .expect("Cannot parse message"); |
58 | |
59 | - let message_id = entry.id(); |
60 | + let message_id = message.message_id(); |
61 | + |
62 | + if message_id.is_none() { |
63 | + return Err(Error::NoMessageId(entry.id().to_string())); |
64 | + } |
65 | + |
66 | + let message_id = message_id.unwrap(); |
67 | + |
68 | let mail_to = [ |
69 | addresses(message.to()), |
70 | // addresses(message.cc()), |
71 | @@ -75,10 +116,9 @@ pub async fn deliver_all(config: &Config) -> Result<Summary, Error> { |
72 | .address() |
73 | .unwrap(); |
74 | |
75 | - let reply_to = message |
76 | - .reply_to() |
77 | - .and_then(|address| address.first()) |
78 | - .and_then(|address| address.address()); |
79 | + let in_reply_to = message.in_reply_to().as_text(); |
80 | + |
81 | + println!("IN REPLY TO: {:?}", in_reply_to); |
82 | |
83 | tracing::info!( |
84 | "Attempting to deliver message from {} to {:?}", |
85 | @@ -92,7 +132,7 @@ pub async fn deliver_all(config: &Config) -> Result<Summary, Error> { |
86 | mail_to.as_slice(), |
87 | mail_from, |
88 | message_bytes.as_slice(), |
89 | - reply_to, |
90 | + in_reply_to, |
91 | ) |
92 | .await |
93 | { |
94 | @@ -107,9 +147,10 @@ pub async fn deliver_all(config: &Config) -> Result<Summary, Error> { |
95 | } else { |
96 | summary.successful.push(message_id.to_string()); |
97 | tracing::info!("Delivered message {}", message_id); |
98 | - if entry.is_seen() { |
99 | - maildir.move_new_to_cur(message_id)?; |
100 | - } |
101 | + // FIXME |
102 | + // if entry.is_seen() { |
103 | + // maildir.move_new_to_cur(message_id)?; |
104 | + // } |
105 | } |
106 | } |
107 | |
108 | diff --git a/ayllu-mail/src/error.rs b/ayllu-mail/src/error.rs |
109 | index f968236..9efdb2b 100644 |
110 | --- a/ayllu-mail/src/error.rs |
111 | +++ b/ayllu-mail/src/error.rs |
112 | @@ -12,5 +12,7 @@ pub enum Error { |
113 | #[error("Io: {0}")] |
114 | Io(#[from] IoError), |
115 | #[error("Maildir: {0}")] |
116 | - Maildir(#[from] MaildirError) |
117 | + Maildir(#[from] MaildirError), |
118 | + #[error("Message has no message id: {0}")] |
119 | + NoMessageId(String) |
120 | } |
121 | diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs |
122 | index 0bd0827..5c4e35e 100644 |
123 | --- a/ayllu/src/web2/routes/mail.rs |
124 | +++ b/ayllu/src/web2/routes/mail.rs |
125 | @@ -7,7 +7,7 @@ use axum::{ |
126 | http::header::CONTENT_TYPE, |
127 | response::{Html, IntoResponse, Response}, |
128 | }; |
129 | - use ayllu_database::{mail::MailExt, Wrapper as Database}; |
130 | + use ayllu_database::{mail::{ListId, MailExt}, Wrapper as Database}; |
131 | use serde::{Deserialize, Serialize}; |
132 | |
133 | use crate::web2::middleware::template::Template; |
134 | @@ -79,12 +79,16 @@ pub async fn threads( |
135 | .lists |
136 | .iter() |
137 | .find(|list| list.name == params.list_name) |
138 | - .ok_or(Error::NotFound(params.list_name))?; |
139 | + .ok_or(Error::NotFound(params.list_name.clone()))?; |
140 | ctx.insert("list", &mailing_list); |
141 | |
142 | // FIXME |
143 | - let threads: Vec<Thread> = Vec::new(); |
144 | + // |
145 | + let list_id = ListId(params.list_name.clone(), String::default()); |
146 | + let threads = db.read_threads(&list_id).await?; |
147 | + let first_message = threads.first(); |
148 | ctx.insert("threads", &threads); |
149 | + ctx.insert("first_message", &first_message); |
150 | let body = templates.render("threads.html", &ctx)?; |
151 | Ok(Html(body)) |
152 | // let mailing_list = lists.iter().find(|list| list.id == params.list_id); |
153 | diff --git a/ayllu/themes/default/templates/threads.html b/ayllu/themes/default/templates/threads.html |
154 | index 57fd7a1..4beb63f 100644 |
155 | --- a/ayllu/themes/default/templates/threads.html |
156 | +++ b/ayllu/themes/default/templates/threads.html |
157 | @@ -28,15 +28,10 @@ |
158 | <h4>Threads</h4> |
159 | <div class="stretch"> |
160 | {% for thread in threads %} |
161 | - <section class="card"> |
162 | - {% set thread = thread.first %} |
163 | - <article class="segmented-3"> |
164 | - <div>{{ thread.from }}</div> |
165 | - <div>{{ thread.timestamp | format_epoch }}</div> |
166 | - <div>{{ thread.n_replies }}</div> |
167 | - </article> |
168 | + <div>{{ thread.from }}</div> |
169 | + <div>{{ thread.timestamp | format_epoch }}</div> |
170 | + <div>{{ thread.n_replies }}</div> |
171 | <a href="/mail/thread/{{ list.id }}/{{ thread.message_id }}">{{ thread.subject }}</a> |
172 | - </section> |
173 | {% endfor %} |
174 | </div> |
175 | </article> |
176 | diff --git a/crates/database/queries/mail_deliver_message.sql b/crates/database/queries/mail_deliver_message.sql |
177 | index 7eb5300..aa8339e 100644 |
178 | --- a/crates/database/queries/mail_deliver_message.sql |
179 | +++ b/crates/database/queries/mail_deliver_message.sql |
180 | @@ -1,7 +1,8 @@ |
181 | INSERT INTO messages |
182 | - (message_id, list_id, reply_to, mail_from, message_body) |
183 | + (message_id, list_id, in_reply_to, mail_from, message_body) |
184 | VALUES ( |
185 | ?, |
186 | ( SELECT id FROM lists WHERE address = ? LIMIT 1 ), |
187 | - ?, ?, ? |
188 | + ( SELECT id FROM messages WHERE message_id = ? LIMIT 1), |
189 | + ?, ? |
190 | ) RETURNING messages.id |
191 | diff --git a/crates/database/queries/mail_read_threads.sql b/crates/database/queries/mail_read_threads.sql |
192 | index c76aa55..6d85b19 100644 |
193 | --- a/crates/database/queries/mail_read_threads.sql |
194 | +++ b/crates/database/queries/mail_read_threads.sql |
195 | @@ -1,3 +1,3 @@ |
196 | SELECT messages.* FROM messages |
197 | LEFT JOIN lists ON lists.id = messages.id |
198 | - WHERE lists.name = ? AND messages.reply_to IS NULL |
199 | + WHERE lists.name = ? AND messages.in_reply_to IS NULL |
200 | diff --git a/crates/database/queries/mail_thread_count.sql b/crates/database/queries/mail_thread_count.sql |
201 | index a021ac4..5dc619a 100644 |
202 | --- a/crates/database/queries/mail_thread_count.sql |
203 | +++ b/crates/database/queries/mail_thread_count.sql |
204 | @@ -1,4 +1,4 @@ |
205 | SELECT COUNT(*) AS "count: i64" FROM messages |
206 | WHERE |
207 | list_id = (SELECT list_id FROM lists WHERE list_id = ?) AND |
208 | - reply_to IS NULL |
209 | + in_reply_to IS NULL |
210 | diff --git a/crates/database/src/mail.rs b/crates/database/src/mail.rs |
211 | index dfcc629..9077a2f 100644 |
212 | --- a/crates/database/src/mail.rs |
213 | +++ b/crates/database/src/mail.rs |
214 | @@ -49,7 +49,7 @@ pub struct Message { |
215 | pub id: i64, |
216 | pub message_id: String, |
217 | pub list_id: i64, |
218 | - pub reply_to: Option<i64>, |
219 | + pub in_reply_to: Option<i64>, |
220 | pub mail_from: i64, |
221 | pub message_body: Option<Vec<u8>>, |
222 | } |
223 | @@ -60,6 +60,12 @@ impl Message { |
224 | } |
225 | } |
226 | |
227 | + #[derive(Clone, Default, Serialize, Deserialize)] |
228 | + pub struct ThreadSummary { |
229 | + pub first_message: Message, |
230 | + pub n_replies: i64 |
231 | + } |
232 | + |
233 | #[async_trait] |
234 | pub trait MailExt { |
235 | /// Ensure that the mailing list configuration reflects the state of the |
236 | @@ -76,7 +82,7 @@ pub trait MailExt { |
237 | mail_to: &[&str], |
238 | mail_from: &str, |
239 | message_body: &[u8], |
240 | - reply_to: Option<&str>, |
241 | + in_reply_to: Option<&str>, |
242 | ) -> Result<Vec<i64>, Error>; |
243 | |
244 | async fn has_message(&self, message_id: &str) -> Result<bool, Error>; |
245 | @@ -85,7 +91,7 @@ pub trait MailExt { |
246 | |
247 | async fn read_message(&self, list_id: &ListId, message_id: &str) -> Result<Message, Error>; |
248 | async fn read_thread(&self, list_id: &ListId, message_id: &str) -> Result<Thread, Error>; |
249 | - async fn read_threads(&self, list_id: &ListId) -> Result<Vec<Message>, Error>; |
250 | + async fn read_threads(&self, list_id: &ListId) -> Result<Vec<ThreadSummary>, Error>; |
251 | } |
252 | |
253 | #[async_trait] |
254 | @@ -157,7 +163,7 @@ impl MailExt for Database { |
255 | mail_to: &[&str], |
256 | mail_from: &str, |
257 | message_body: &[u8], |
258 | - reply_to: Option<&str>, |
259 | + in_reply_to: Option<&str>, |
260 | ) -> Result<Vec<i64>, Error> { |
261 | let mut tx = self.pool.begin().await?; |
262 | let participant_id = sqlx::query_file!( |
263 | @@ -175,7 +181,7 @@ impl MailExt for Database { |
264 | "queries/mail_deliver_message.sql", |
265 | message_id, |
266 | mail_to_addr, |
267 | - reply_to, |
268 | + in_reply_to, |
269 | participant_id, |
270 | message_body, |
271 | ) |
272 | @@ -216,11 +222,11 @@ impl MailExt for Database { |
273 | Ok(Thread(vec![message])) |
274 | } |
275 | |
276 | - async fn read_threads(&self, list_id: &ListId) -> Result<Vec<Message>, Error> { |
277 | + async fn read_threads(&self, list_id: &ListId) -> Result<Vec<ThreadSummary>, Error> { |
278 | let list_name = list_id.0.as_str(); |
279 | let messages = sqlx::query_file_as!(Message, "queries/mail_read_threads.sql", list_name) |
280 | .fetch_all(&self.pool) |
281 | .await?; |
282 | - Ok(messages) |
283 | + Ok(Vec::new()) |
284 | } |
285 | } |
286 | diff --git a/migrations/20241028092919_mail.sql b/migrations/20241028092919_mail.sql |
287 | index e5f36cb..a346854 100644 |
288 | --- a/migrations/20241028092919_mail.sql |
289 | +++ b/migrations/20241028092919_mail.sql |
290 | @@ -31,7 +31,7 @@ CREATE TABLE messages ( |
291 | id INTEGER PRIMARY KEY, |
292 | message_id TEXT NOT NULL UNIQUE, |
293 | list_id INTEGER NOT NULL REFERENCES lists(id), |
294 | - reply_to INTEGER REFERENCES messages(id), |
295 | + in_reply_to INTEGER REFERENCES messages(id), |
296 | mail_from INTEGER NOT NULL REFERENCES participants(id), |
297 | message_body BLOB |
298 | ) STRICT ; |