Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 996865c3b0a42da81d9306912fe2456e4d6fa3bf
Timestamp: Sun, 01 Dec 2024 17:29:14 +0000 (2 months ago)

+84 -35 +/-9 browse
more mail delivery work
1diff --git a/ayllu-mail/src/delivery.rs b/ayllu-mail/src/delivery.rs
2index 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
109index 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
122index 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
154index 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
177index 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
192index 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
201index 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
211index 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
287index 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 ;