Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: ed6a0aec22ec52ce80b12672a72aa57dbdca3eb1
Timestamp: Fri, 12 Jan 2024 18:16:35 +0000 (1 year ago)

+314 -207 +/-7 browse
move list lookup into separate rpc method
1diff --git a/ayllu-mail/src/server.rs b/ayllu-mail/src/server.rs
2index 6f26d66..87f89e0 100644
3--- a/ayllu-mail/src/server.rs
4+++ b/ayllu-mail/src/server.rs
5 @@ -1,5 +1,4 @@
6 use std::error::Error as StdError;
7- use std::io::{Read, Write};
8
9 use anyhow::{format_err, Result};
10 use capnp::{capability::Promise, Error as CapnpError};
11 @@ -8,16 +7,13 @@ use mailpot::{
12 models::{DbVal, Post},
13 Connection, Error as MailpotError,
14 };
15- use melib::{
16- mbox::{MboxFormat, MboxMetadata},
17- Envelope,
18- };
19+ use melib::Envelope;
20 use tracing::log;
21
22 use crate::config::Config;
23 use ayllu_api::mail_capnp::server::{
24- Client, ExportParams, ExportResults, ListThreadsParams, ListThreadsResults, ReadPostParams,
25- ReadPostResults, ReadThreadParams, ReadThreadResults, Server,
26+ Client, ExportParams, ExportResults, ListThreadsParams, ListThreadsResults, ListsParams,
27+ ListsResults, ReadPostParams, ReadPostResults, ReadThreadParams, ReadThreadResults, Server,
28 };
29 use ayllu_rpc::Server as RpcHelper;
30
31 @@ -208,6 +204,53 @@ impl Server for ServerImpl {
32 Err(e) => Promise::err(CapnpError::failed(e.to_string())),
33 }
34 }
35+
36+ fn lists(
37+ &mut self,
38+ _: ListsParams,
39+ results: ListsResults,
40+ ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
41+ let read_lists = |mut results: ListsResults| {
42+ let lists = self.db.lists()?;
43+ let mut lists_array = results.get().init_lists(lists.len() as u32);
44+ for (i, list) in lists.iter().enumerate() {
45+ lists_array
46+ .reborrow()
47+ .get(i as u32)
48+ .set_id(list.id.as_str().into());
49+ lists_array
50+ .reborrow()
51+ .get(i as u32)
52+ .set_name(list.name.as_str().into());
53+ lists_array
54+ .reborrow()
55+ .get(i as u32)
56+ .set_address(list.address.as_str().into());
57+ lists_array
58+ .reborrow()
59+ .get(i as u32)
60+ .set_request_address(list.request_subaddr().as_str().into());
61+ if let Some(description) = &list.description {
62+ lists_array
63+ .reborrow()
64+ .get(i as u32)
65+ .set_name(description.as_str().into());
66+ }
67+ let mut topics = lists_array
68+ .reborrow()
69+ .get(i as u32)
70+ .init_topics(list.topics.len() as u32);
71+ for (j, topic) in list.topics.iter().enumerate() {
72+ topics.set(j as u32, topic.as_str().into());
73+ }
74+ }
75+ Ok::<(), MailpotError>(())
76+ };
77+ match read_lists(results) {
78+ Ok(_) => Promise::ok(()),
79+ Err(e) => Promise::err(CapnpError::failed(e.to_string())),
80+ }
81+ }
82 }
83
84 pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
85 diff --git a/crates/api/v1/mail.capnp b/crates/api/v1/mail.capnp
86index b66637a..8e7c791 100644
87--- a/crates/api/v1/mail.capnp
88+++ b/crates/api/v1/mail.capnp
89 @@ -1,10 +1,5 @@
90 @0xe282ac1f72de3195;
91
92- struct ThreadedMessage {
93- depth @0 :Int64;
94- message @1 :Message;
95- }
96-
97 # a single email
98 struct Message {
99 # primary key of the message in the mailpot db
100 @@ -31,13 +26,30 @@ struct Thread {
101 hasPatch @2 :Bool;
102 }
103
104+ struct MailingList {
105+ # unique list identifier
106+ id @0 :Text;
107+ # freindly mailing list name
108+ name @1 :Text;
109+ # address to send "commands" to
110+ requestAddress @2 :Text;
111+ # RFC 5322 email address
112+ address @3 :Text;
113+ # description of the mailing list purpose
114+ description @4 :Text;
115+ # free form array of tags
116+ topics @5 :List(Text);
117+ }
118+
119 interface Server {
120+ # return all managed mailing lists
121+ lists @0 () -> (lists: List(MailingList));
122 # list all of the threads associated with a mailing list
123- listThreads @0 (id: Text, offset: Int64, limit: Int64) -> (threads: List(Thread));
124+ listThreads @1 (id: Text, offset: Int64, limit: Int64) -> (threads: List(Thread));
125 # list all of the responses associated with a post starting from messageId
126- readThread @1 (id: Text, messageId: Text, offset: Int64, limit: Int64) -> (thread: List(Message));
127+ readThread @2 (id: Text, messageId: Text, offset: Int64, limit: Int64) -> (thread: List(Message));
128 # read a single post
129- readPost @2 (id: Text, messageId: Text) -> Message;
130+ readPost @3 (id: Text, messageId: Text) -> Message;
131 # export one or more messages in mbox format
132- export @3 (id: Text, messageId: Text, asThread: Bool) -> (thread :Data);
133+ export @4 (id: Text, messageId: Text, asThread: Bool) -> (thread :Data);
134 }
135 diff --git a/src/config.rs b/src/config.rs
136index 0895b9d..66a91ed 100644
137--- a/src/config.rs
138+++ b/src/config.rs
139 @@ -175,28 +175,9 @@ impl Xmpp {
140 }
141
142 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
143- pub struct SubscriptionPolicy {
144- pub send_confirmation: bool,
145- pub kind: String,
146- }
147-
148- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
149- pub struct MailingList {
150- pub id: String,
151- pub name: Option<String>,
152- pub address: String,
153- pub request_address: String,
154- pub description: String,
155- pub topics: Vec<String>,
156- pub post_policy: String,
157- pub subscription_policy: SubscriptionPolicy,
158- }
159-
160- #[derive(Deserialize, Serialize, Clone, Debug, Default)]
161 pub struct Mail {
162 #[serde(default = "Mail::default_socket_path")]
163 pub socket_path: String,
164- pub lists: Vec<MailingList>,
165 }
166
167 impl Mail {
168 diff --git a/src/web2/routes/mail.rs b/src/web2/routes/mail.rs
169index c67a43a..2991176 100644
170--- a/src/web2/routes/mail.rs
171+++ b/src/web2/routes/mail.rs
172 @@ -7,7 +7,6 @@ use axum::{
173 };
174 use serde::{Deserialize, Serialize};
175
176- use crate::config::Config;
177 use crate::languages::Hint;
178 use crate::web2::error::Error;
179 use crate::web2::highlight::Highlighter;
180 @@ -15,6 +14,7 @@ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
181 use crate::web2::middleware::template::Template;
182 use crate::web2::util::navigation;
183 use ayllu_api::mail_capnp::server::Client as MailClient;
184+ use ayllu_rpc::Client;
185
186 #[derive(Deserialize)]
187 pub struct Params {
188 @@ -45,14 +45,83 @@ struct Message {
189 pub is_patch: bool,
190 }
191
192+ #[derive(Debug, Serialize, Default, PartialEq)]
193+ struct List {
194+ pub id: String,
195+ pub name: String,
196+ pub description: Option<String>,
197+ pub address: String,
198+ pub topics: Vec<String>,
199+ pub request_address: String,
200+ }
201+
202+ async fn read_list(mail_client: Client, list_id: String) -> Result<Option<List>, Error> {
203+ let list = mail_client
204+ .invoke(move |c: MailClient| async move {
205+ let req = c.lists_request();
206+ let result = req.send().promise.await?;
207+ let rpc_lists = result.get()?.get_lists()?;
208+ for list in rpc_lists.iter() {
209+ let id = list.get_id()?.to_string().unwrap();
210+ let topics: Vec<String> = list
211+ .get_topics()?
212+ .iter()
213+ .map(|topic| topic.unwrap().to_string().unwrap())
214+ .collect();
215+ if list_id == id {
216+ return Ok(Some(List {
217+ id: list_id.clone(),
218+ name: list.get_name()?.to_string().unwrap(),
219+ description: None,
220+ address: list.get_address()?.to_string().unwrap(),
221+ topics,
222+ request_address: list.get_request_address()?.to_string().unwrap(),
223+ }));
224+ }
225+ }
226+ Ok(None)
227+ })
228+ .await?;
229+ Ok(list)
230+ }
231+
232 pub async fn lists(
233- Extension(cfg): Extension<Config>,
234+ Extension(initiator): Extension<Initiator>,
235 Extension((templates, mut ctx)): Extension<Template>,
236 ) -> Result<Html<String>, Error> {
237 ctx.insert("title", "lists");
238 ctx.insert("nav_elements", &navigation::global("mail", true));
239- // TODO: add stats method and display like xmpp
240- ctx.insert("lists", &cfg.mail.unwrap().lists.clone());
241+ let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
242+ let lists = mail_client
243+ .invoke(move |c: MailClient| async move {
244+ let mut lists: Vec<List> = Vec::new();
245+ let req = c.lists_request();
246+ let result = req.send().promise.await?;
247+ let rpc_lists = result.get()?.get_lists()?;
248+ for list in rpc_lists.iter() {
249+ let description = if list.has_description() {
250+ Some(list.get_description()?.to_string().unwrap())
251+ } else {
252+ None
253+ };
254+ let topics: Vec<String> = list
255+ .get_topics()?
256+ .iter()
257+ .map(|topic| topic.unwrap().to_string().unwrap())
258+ .collect();
259+ lists.push(List {
260+ id: list.get_id()?.to_string().unwrap(),
261+ name: list.get_name()?.to_string().unwrap(),
262+ description,
263+ address: list.get_name()?.to_string().unwrap(),
264+ topics,
265+ request_address: list.get_request_address()?.to_string().unwrap(),
266+ })
267+ }
268+ Ok(lists)
269+ })
270+ .await?;
271+ ctx.insert("lists", &lists);
272 // ctx.insert("lists", &cfg.mail.unwrap().lists);
273 let body = templates.render("lists.html", &ctx)?;
274 Ok(Html(body))
275 @@ -63,76 +132,133 @@ pub async fn threads(
276 Path(params): Path<Params>,
277 // Extension(db): Extension<Arc<Database>>,
278 Extension(initiator): Extension<Initiator>,
279- Extension(cfg): Extension<Config>,
280 Extension((templates, mut ctx)): Extension<Template>,
281 ) -> Result<Html<String>, Error> {
282- let lists = cfg.mail.unwrap().lists;
283- let list = match lists.iter().find(|list| list.id == params.list_id) {
284- Some(list) => Ok(list),
285- None => Err(Error::Message(format!(
286- "no list associated with: {}",
287+ let client = initiator.client(InitiatorKind::Mail).unwrap();
288+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
289+ if let Some(mailing_list) = mailing_list {
290+ ctx.insert("title", &format!("list {}", mailing_list.name));
291+ ctx.insert("nav_elements", &navigation::global("mail", true));
292+ ctx.insert("list", &mailing_list);
293+ ctx.insert("title", &format!("list {}", mailing_list.address));
294+ ctx.insert("nav_elements", &navigation::global("mail", true));
295+ ctx.insert("list", &mailing_list);
296+ let mut threads = client
297+ .invoke(move |c: MailClient| async move {
298+ let mut threads: Vec<Thread> = Vec::new();
299+ let mut req = c.list_threads_request();
300+ req.get().set_id(params.list_id.as_str().into());
301+ let result = req.send().promise.await?;
302+ let rpc_threads = result.get()?.get_threads()?;
303+ for thread in rpc_threads.iter() {
304+ let message = thread.get_first()?;
305+ let message_id = message.get_message_id()?.to_string().unwrap();
306+ threads.push(Thread {
307+ id: message.get_id(),
308+ message_id,
309+ from: message.get_from()?.to_string().unwrap(),
310+ subject: message.get_subject()?.to_string().unwrap(),
311+ n_replies: thread.get_n_replies(),
312+ timestamp: thread.get_first().unwrap().get_timestamp(),
313+ })
314+ }
315+ Ok(threads)
316+ })
317+ .await?;
318+ threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
319+ ctx.insert("threads", &threads);
320+ ctx.insert("request_address", &mailing_list.request_address);
321+ let body = templates.render("threads.html", &ctx)?;
322+ Ok(Html(body))
323+ } else {
324+ Err(Error::NotFound(format!(
325+ "no mailing list with id: {} exists",
326 params.list_id
327- ))),
328- }?;
329- ctx.insert("title", &format!("list {}", list.address));
330- ctx.insert("nav_elements", &navigation::global("mail", true));
331- ctx.insert("list", list);
332-
333- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
334- let mut threads = mail_client
335- .invoke(move |c: MailClient| async move {
336- let mut threads: Vec<Thread> = Vec::new();
337- let mut req = c.list_threads_request();
338- req.get().set_id(params.list_id.as_str().into());
339- let result = req.send().promise.await?;
340- let rpc_threads = result.get()?.get_threads()?;
341- for thread in rpc_threads.iter() {
342- let message = thread.get_first()?;
343- let message_id = message.get_message_id()?.to_string().unwrap();
344- threads.push(Thread {
345- id: message.get_id(),
346- message_id,
347- from: message.get_from()?.to_string().unwrap(),
348- subject: message.get_subject()?.to_string().unwrap(),
349- n_replies: thread.get_n_replies(),
350- timestamp: thread.get_first().unwrap().get_timestamp(),
351- })
352- }
353- Ok(threads)
354- })
355- .await?;
356- threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
357- ctx.insert("threads", &threads);
358- ctx.insert("request_email", &list.request_address);
359- let body = templates.render("threads.html", &ctx)?;
360- Ok(Html(body))
361+ )))
362+ }
363 }
364
365 pub async fn thread(
366 Path(params): Path<Params>,
367 Extension(initiator): Extension<Initiator>,
368 Extension(highlighter): Extension<Highlighter>,
369- Extension(cfg): Extension<Config>,
370 Extension((templates, mut ctx)): Extension<Template>,
371 ) -> Result<Html<String>, Error> {
372- let lists = cfg.mail.unwrap().lists;
373- let list = match lists.iter().find(|list| list.id == params.list_id) {
374- Some(list) => Ok(list),
375- None => Err(Error::Message(format!(
376- "no list associated with: {}",
377+ let client = initiator.client(InitiatorKind::Mail).unwrap();
378+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
379+ if let Some(mailing_list) = mailing_list {
380+ let thread_id = params.thread_id.clone();
381+ let messages = client
382+ .invoke(move |c: MailClient| async move {
383+ let mut messages: Vec<Message> = Vec::new();
384+ let mut req = c.read_thread_request();
385+ req.get().set_id(params.list_id.as_str().into());
386+ req.get().set_message_id(thread_id.unwrap().as_str().into());
387+ let result = req.send().promise.await?;
388+ for message in result.get()?.get_thread()? {
389+ let text = message.get_text().unwrap().to_string().unwrap();
390+ let text = if message.get_is_patch() {
391+ let (_, diff) = highlighter.highlight(
392+ &text,
393+ None,
394+ None,
395+ Some(Hint::from("DIFF")),
396+ false,
397+ );
398+ diff
399+ } else {
400+ text
401+ };
402+ messages.push(Message {
403+ id: message.get_id().to_string(),
404+ subject: message.get_subject()?.to_string().unwrap(),
405+ message_id: message.get_message_id()?.to_string().unwrap(),
406+ created_at: message.get_timestamp(),
407+ from_address: message.get_address()?.to_string().unwrap(),
408+ body: message.get_body()?.to_string().unwrap(),
409+ text,
410+ is_patch: message.get_is_patch(),
411+ })
412+ }
413+ Ok(messages)
414+ })
415+ .await?;
416+ let subject = messages.first().map(|message| message.subject.clone());
417+ ctx.insert("title", &format!("list {}", mailing_list.address));
418+ ctx.insert("nav_elements", &navigation::global("mail", true));
419+ ctx.insert("list", &mailing_list);
420+ ctx.insert("list_id", &mailing_list.id);
421+ ctx.insert("thread_id", &params.thread_id.clone());
422+ ctx.insert("messages", &messages);
423+ ctx.insert("subject", &subject);
424+ let body = templates.render("thread.html", &ctx)?;
425+ Ok(Html(body))
426+ } else {
427+ Err(Error::NotFound(format!(
428+ "no mailing list with id: {} exists",
429 params.list_id
430- ))),
431- }?;
432- let thread_id = params.thread_id.clone();
433- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
434- let messages = mail_client
435- .invoke(move |c: MailClient| async move {
436- let mut messages: Vec<Message> = Vec::new();
437- let mut req = c.read_thread_request();
438- req.get().set_id(params.list_id.as_str().into());
439- req.get().set_message_id(thread_id.unwrap().as_str().into());
440- let result = req.send().promise.await?;
441- for message in result.get()?.get_thread()? {
442+ )))
443+ }
444+ }
445+
446+ pub async fn message(
447+ Path(params): Path<Params>,
448+ Extension(initiator): Extension<Initiator>,
449+ Extension(highlighter): Extension<Highlighter>,
450+ Extension((templates, mut ctx)): Extension<Template>,
451+ ) -> Result<Html<String>, Error> {
452+ let client = initiator.client(InitiatorKind::Mail).unwrap();
453+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
454+ if let Some(mailing_list) = mailing_list {
455+ let message = client
456+ .invoke(move |c: MailClient| async move {
457+ let mut req = c.read_post_request();
458+ req.get().set_id(params.list_id.as_str().into());
459+ req.get()
460+ .set_message_id(params.message_id.unwrap().as_str().into());
461+ // TODO: detect a missing message and return 404 appropriately
462+ let result = req.send().promise.await?;
463+ let message = result.get()?;
464 let text = message.get_text().unwrap().to_string().unwrap();
465 let text = if message.get_is_patch() {
466 let (_, diff) =
467 @@ -141,117 +267,64 @@ pub async fn thread(
468 } else {
469 text
470 };
471- messages.push(Message {
472+ Ok(Message {
473 id: message.get_id().to_string(),
474 subject: message.get_subject()?.to_string().unwrap(),
475 message_id: message.get_message_id()?.to_string().unwrap(),
476 created_at: message.get_timestamp(),
477- from_address: message.get_address()?.to_string().unwrap(),
478 body: message.get_body()?.to_string().unwrap(),
479 text,
480+ from_address: message.get_address()?.to_string().unwrap(),
481 is_patch: message.get_is_patch(),
482 })
483- }
484- Ok(messages)
485- })
486- .await?;
487- let subject = messages.first().map(|message| message.subject.clone());
488- ctx.insert("title", &format!("list {}", list.address));
489- ctx.insert("nav_elements", &navigation::global("mail", true));
490- ctx.insert("list", list);
491- ctx.insert("list_id", &list.id);
492- ctx.insert("thread_id", &params.thread_id.clone());
493- ctx.insert("messages", &messages);
494- ctx.insert("subject", &subject);
495- let body = templates.render("thread.html", &ctx)?;
496- Ok(Html(body))
497- }
498-
499- pub async fn message(
500- Path(params): Path<Params>,
501- Extension(initiator): Extension<Initiator>,
502- Extension(highlighter): Extension<Highlighter>,
503- Extension(cfg): Extension<Config>,
504- Extension((templates, mut ctx)): Extension<Template>,
505- ) -> Result<Html<String>, Error> {
506- let lists = cfg.mail.unwrap().lists;
507- let list = match lists.iter().find(|list| list.id == params.list_id) {
508- Some(list) => Ok(list),
509- None => Err(Error::Message(format!(
510- "no list associated with: {}",
511- params.list_id
512- ))),
513- }?;
514- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
515- let message = mail_client
516- .invoke(move |c: MailClient| async move {
517- let mut req = c.read_post_request();
518- req.get().set_id(params.list_id.as_str().into());
519- req.get()
520- .set_message_id(params.message_id.unwrap().as_str().into());
521- let result = req.send().promise.await?;
522- let message = result.get()?;
523- let text = message.get_text().unwrap().to_string().unwrap();
524- let text = if message.get_is_patch() {
525- let (_, diff) =
526- highlighter.highlight(&text, None, None, Some(Hint::from("DIFF")), false);
527- diff
528- } else {
529- text
530- };
531- Ok(Message {
532- id: message.get_id().to_string(),
533- subject: message.get_subject()?.to_string().unwrap(),
534- message_id: message.get_message_id()?.to_string().unwrap(),
535- created_at: message.get_timestamp(),
536- body: message.get_body()?.to_string().unwrap(),
537- text,
538- from_address: message.get_address()?.to_string().unwrap(),
539- is_patch: message.get_is_patch(),
540 })
541- })
542- .await?;
543- ctx.insert("title", &format!("list {}", list.address));
544- ctx.insert("nav_elements", &navigation::global("mail", true));
545- ctx.insert("list", list);
546- ctx.insert("list_id", &list.id);
547- ctx.insert("thread_id", &params.thread_id);
548- ctx.insert("message", &message);
549- let body = templates.render("post.html", &ctx)?;
550- Ok(Html(body))
551+ .await?;
552+ ctx.insert("title", &format!("list {}", mailing_list.name));
553+ ctx.insert("nav_elements", &navigation::global("mail", true));
554+ ctx.insert("list", &mailing_list);
555+ ctx.insert("list_id", &mailing_list.id.clone());
556+ ctx.insert("thread_id", &params.thread_id);
557+ ctx.insert("message", &message);
558+ let body = templates.render("post.html", &ctx)?;
559+ Ok(Html(body))
560+ } else {
561+ Err(Error::NotFound(format!(
562+ "no mailing list with id: {} exists",
563+ params.list_id
564+ )))
565+ }
566 }
567
568 pub async fn export(
569 Path(params): Path<Params>,
570 Extension(initiator): Extension<Initiator>,
571- Extension(cfg): Extension<Config>,
572 ) -> Result<Response, Error> {
573- let lists = cfg.mail.unwrap().lists;
574- let list = match lists.iter().find(|list| list.id == params.list_id) {
575- Some(list) => Ok(list),
576- None => Err(Error::Message(format!(
577- "no list associated with: {}",
578+ let client = initiator.client(InitiatorKind::Mail).unwrap();
579+ let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
580+ if let Some(mailing_list) = mailing_list {
581+ let list_id = mailing_list.id.clone();
582+ let thread = client
583+ .invoke(move |c: MailClient| async move {
584+ let mut req = c.export_request();
585+ req.get().set_id(list_id.as_str().into());
586+ if let Some(message_id) = params.message_id {
587+ req.get().set_message_id(message_id.as_str().into());
588+ req.get().set_as_thread(true);
589+ }
590+ let result = req.send().promise.await?;
591+ let post = result.get()?.get_thread()?;
592+ Ok(post.to_vec())
593+ })
594+ .await?;
595+ let mut response = Bytes::from(thread).into_response();
596+ response
597+ .headers_mut()
598+ .insert(CONTENT_TYPE, "application/mbox".parse().unwrap());
599+ Ok(response)
600+ } else {
601+ Err(Error::NotFound(format!(
602+ "no mailing list with id: {} exists",
603 params.list_id
604- ))),
605- }?;
606- let list_id = list.id.clone();
607- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
608- let thread = mail_client
609- .invoke(move |c: MailClient| async move {
610- let mut req = c.export_request();
611- req.get().set_id(list_id.as_str().into());
612- if let Some(message_id) = params.message_id {
613- req.get().set_message_id(message_id.as_str().into());
614- req.get().set_as_thread(true);
615- }
616- let result = req.send().promise.await?;
617- let post = result.get()?.get_thread()?;
618- Ok(post.to_vec())
619- })
620- .await?;
621- let mut response = Bytes::from(thread).into_response();
622- response
623- .headers_mut()
624- .insert(CONTENT_TYPE, "application/mbox".parse().unwrap());
625- Ok(response)
626+ )))
627+ }
628 }
629 diff --git a/src/web2/routes/repo.rs b/src/web2/routes/repo.rs
630index 1b4049a..43d156a 100644
631--- a/src/web2/routes/repo.rs
632+++ b/src/web2/routes/repo.rs
633 @@ -246,6 +246,7 @@ pub async fn serve(
634 ctx.insert("chat_links", &chat_links);
635
636 // find any associated mailing lists associated with this repository
637+ /*
638 let email_links = preamble.config.clone().mail.map(|mail| {
639 let links: Vec<EmailLink> = mail
640 .iter()
641 @@ -271,6 +272,7 @@ pub async fn serve(
642 });
643
644 ctx.insert("email_links", &email_links);
645+ */
646
647 let materialized = make_view(
648 &db,
649 diff --git a/themes/default/templates/lists.html b/themes/default/templates/lists.html
650index 0196b96..afbcce5 100644
651--- a/themes/default/templates/lists.html
652+++ b/themes/default/templates/lists.html
653 @@ -16,9 +16,9 @@
654 <tbody>
655 {% for list in lists %}
656 <tr>
657- <td>{{ list.id }}</td>
658+ <td><a href="/mail/{{list.id}}">{{ list.id }}</a></td>
659 <td>{{ list.name }}</td>
660- <td><a href="/mail/{{list.id}}">{{ list.description }}</a></td>
661+ <td>{{ list.description }}</td>
662 <td>{{ list.address }}</td>
663 </tr>
664 {% endfor %}
665 diff --git a/themes/default/templates/threads.html b/themes/default/templates/threads.html
666index 83470aa..1b2c733 100644
667--- a/themes/default/templates/threads.html
668+++ b/themes/default/templates/threads.html
669 @@ -5,23 +5,19 @@
670 <article>
671 <header>
672 <h1> {{ list.name }} </h1>
673+ <span class="right labels">
674+ {% for topic in list.topics %}
675+ <span class="feature">{{topic}}</span>
676+ {% endfor %}
677+ </span>
678 </header>
679 <div class="mailing-list-details">
680 <h4> {{ list.description }} </h4>
681 </br>
682- <span class="labels">
683- {% for topic in list.topics %}
684- <span class="feature">{{topic}}</span>
685- {% endfor %}
686- </br><b>Post Policy = {{list.post_policy}} </b>
687- </br><b>Subscription Policy = {{list.subscription_policy.kind}}</b>
688- </br><b>Send Confirmation = {{list.subscription_policy.send_confirmation}}</b>
689- </br>
690- </span></br>
691 <h4> Subscribe </h4>
692- <p> Send an e-mail to <a href="mailto:{{ request_email }}?subject=subscribe">{{request_email}}</a> with the following subject: <code>subscribe</code> </p>
693+ <p> Send an e-mail to <a href="mailto:{{request_address}}?subject=subscribe">{{request_address}}</a> with the following subject: <code>subscribe</code> </p>
694 <h4> Unsubscribe </h4>
695- <p> Send an e-mail to <a href="mailto:{{ request_email }}?subject=unsubscribe">{{request_email}}</a> with the following subject: <code>unsubscribe</code> </p>
696+ <p> Send an e-mail to <a href="mailto:{{ request_address}}?subject=unsubscribe">{{request_address}}</a> with the following subject: <code>unsubscribe</code> </p>
697 <h4> Export List </h4>
698 <p><a href="/mail/export/{{list.id}}">mbox</a></p>
699 </div>