core: impl help request emails

grcov
Manos Pitsidianakis 2023-04-21 17:29:27 +03:00
parent e9d05fce2e
commit 876e32bb76
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
6 changed files with 140 additions and 34 deletions

View File

@ -220,7 +220,8 @@ impl Connection {
details => &reason,
},
queue: Queue::Out,
comment: format!("PostAction::Reject {{ reason: {} }}", reason),
comment: format!("PostAction::Reject {{ reason: {} }}", reason)
.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -242,7 +243,8 @@ impl Connection {
details => &reason,
},
queue: Queue::Out,
comment: format!("PostAction::Defer {{ reason: {} }}", reason),
comment: format!("PostAction::Defer {{ reason: {} }}", reason)
.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -283,6 +285,35 @@ impl Connection {
) -> Result<()> {
let post_policy = self.list_post_policy(list.pk)?;
match request {
ListRequest::Help => {
// [ref:TODO] add test for this
trace!(
"help action for addresses {:?} in list {}",
env.from(),
list
);
let subscription_policy = self.list_subscription_policy(list.pk)?;
let subject = format!("Help for {}", list.name);
let details = list
.generate_help_email(post_policy.as_deref(), subscription_policy.as_deref());
for f in env.from() {
self.send_reply_with_list_template(
TemplateRenderContext {
template: Template::GENERIC_HELP,
default_fn: Some(Template::default_generic_help),
list,
context: minijinja::context! {
list => &list,
subject => &subject,
details => &details,
},
queue: Queue::Out,
comment: "Help request".into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
}
}
ListRequest::Subscribe => {
trace!(
"subscribe action for addresses {:?} in list {}",
@ -311,7 +342,7 @@ impl Connection {
details => "No action has been taken since you are already subscribed to the list.",
},
queue: Queue::Out,
comment: format!("Address {} is already subscribed to list {}", f, list.id),
comment: format!("Address {} is already subscribed to list {}", f, list.id).into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -348,8 +379,7 @@ impl Connection {
candidate => &v,
},
queue: Queue::Out,
comment: Template::SUBSCRIPTION_REQUEST_NOTICE_OWNER
.to_string(),
comment: Template::SUBSCRIPTION_REQUEST_NOTICE_OWNER.into(),
},
list_owners.iter().map(|owner| Cow::Owned(owner.address())),
)?;
@ -371,7 +401,8 @@ impl Connection {
comment: format!(
"Could not create candidate subscription for {f:?}: \
{err}"
),
)
.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -392,7 +423,8 @@ impl Connection {
comment: format!(
"Could not create candidate subscription for {f:?}: \
{err}"
),
)
.into(),
},
list_owners.iter().map(|owner| Cow::Owned(owner.address())),
)?;
@ -412,7 +444,8 @@ impl Connection {
list => &list,
},
queue: Queue::Out,
comment: format!("Could not create subscription for {f:?}: {err}"),
comment: format!("Could not create subscription for {f:?}: {err}")
.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -430,7 +463,8 @@ impl Connection {
details => err.to_string(),
},
queue: Queue::Out,
comment: format!("Could not create subscription for {f:?}: {err}"),
comment: format!("Could not create subscription for {f:?}: {err}")
.into(),
},
list_owners.iter().map(|owner| Cow::Owned(owner.address())),
)?;
@ -448,7 +482,7 @@ impl Connection {
list => &list,
},
queue: Queue::Out,
comment: Template::SUBSCRIPTION_CONFIRMATION.to_string(),
comment: Template::SUBSCRIPTION_CONFIRMATION.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -475,7 +509,7 @@ impl Connection {
list => &list,
},
queue: Queue::Out,
comment: format!("Could not unsubscribe {f:?}: {err}"),
comment: format!("Could not unsubscribe {f:?}: {err}").into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -493,7 +527,7 @@ impl Connection {
details => err.to_string(),
},
queue: Queue::Out,
comment: format!("Could not unsubscribe {f:?}: {err}"),
comment: format!("Could not unsubscribe {f:?}: {err}").into(),
},
list_owners.iter().map(|owner| Cow::Owned(owner.address())),
)?;
@ -507,7 +541,7 @@ impl Connection {
list => &list,
},
queue: Queue::Out,
comment: Template::UNSUBSCRIPTION_CONFIRMATION.to_string(),
comment: Template::UNSUBSCRIPTION_CONFIRMATION.into(),
},
std::iter::once(Cow::Borrowed(f)),
)?;
@ -536,9 +570,8 @@ impl Connection {
}
ListRequest::Other(ref req) if req.trim().eq_ignore_ascii_case("password") => {
trace!(
"list-request password set action for addresses {:?} in list {}",
"list-request password set action for addresses {:?} in list {list}",
env.from(),
list
);
let body = env.body_bytes(raw);
let password = body.text();
@ -574,38 +607,31 @@ impl Connection {
}
ListRequest::RetrieveMessages(ref message_ids) => {
trace!(
"retrieve messages {:?} action for addresses {:?} in list {}",
message_ids,
"retrieve messages {message_ids:?} action for addresses {:?} in list {list}",
env.from(),
list
);
return Err("message retrievals are not implemented yet.".into());
}
ListRequest::RetrieveArchive(ref from, ref to) => {
trace!(
"retrieve archive action from {:?} to {:?} for addresses {:?} in list {}",
from,
to,
"retrieve archive action from {from:?} to {to:?} for addresses {:?} in list \
{list}",
env.from(),
list
);
return Err("message retrievals are not implemented yet.".into());
}
ListRequest::SetDigest(ref toggle) => {
ListRequest::ChangeSetting(ref setting, ref toggle) => {
trace!(
"set digest action with value {} for addresses {:?} in list {}",
toggle,
"change setting {setting}, request with value {toggle:?} for addresses {:?} \
in list {list}",
env.from(),
list
);
return Err("setting digest options via e-mail is not implemented yet.".into());
}
ListRequest::Other(ref req) => {
trace!(
"unknown request action {} for addresses {:?} in list {}",
req,
"unknown request action {req} for addresses {:?} in list {list}",
env.from(),
list
);
return Err(format!("Unknown request {req}.").into());
}
@ -712,7 +738,7 @@ impl Connection {
Some(list.pk),
None,
draft.finalise()?.as_bytes(),
Some(comment.clone()),
Some(comment.to_string()),
)?)?;
}
Ok(())
@ -733,5 +759,5 @@ pub struct TemplateRenderContext<'ctx, F: Fn() -> Template> {
/// Destination queue in the database.
pub queue: Queue,
/// Comment for the queue entry in the database.
pub comment: String,
pub comment: Cow<'static, str>,
}

View File

@ -123,6 +123,8 @@ pub enum MailJob {
/// Type of mailing list request.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ListRequest {
/// Get help about a mailing list and its available interfaces.
Help,
/// Request subscription.
Subscribe,
/// Request removal of subscription.
@ -132,8 +134,9 @@ pub enum ListRequest {
/// Request reception of specific mailing list posts from `Message-ID`
/// values.
RetrieveMessages(Vec<String>),
/// Request change in digest preferences. (See [`ListSubscription`])
SetDigest(bool),
/// Request change in subscription settings.
/// See [`ListSubscription`].
ChangeSetting(String, bool),
/// Other type of request.
Other(String),
}
@ -152,8 +155,10 @@ impl<S: AsRef<str>> TryFrom<(S, &melib::Envelope)> for ListRequest {
Ok(match val {
"subscribe" | "request" if env.subject().trim() == "subscribe" => Self::Subscribe,
"unsubscribe" | "request" if env.subject().trim() == "unsubscribe" => Self::Unsubscribe,
"help" => Self::Help,
"request" => Self::Other(env.subject().trim().to_string()),
_ => {
// [ref:TODO] add ChangeSetting parsing
trace!("unknown action = {} for addresses {:?}", val, env.from(),);
Self::Other(val.trim().to_string())
}

View File

@ -23,6 +23,8 @@
use super::*;
pub mod changesets;
use std::borrow::Cow;
use melib::email::Address;
/// A database entry and its primary key. Derefs to its inner type.
@ -257,6 +259,65 @@ impl MailingList {
}
}
}
/// Generate help e-mail body containing information on how to subscribe,
/// unsubscribe, post and how to contact the list owners.
pub fn generate_help_email(
&self,
post_policy: Option<&PostPolicy>,
subscription_policy: Option<&SubscriptionPolicy>,
) -> String {
format!(
"Help for {list_name}\n\n{subscribe}\n\n{post}\n\nTo contact the list owners, send an \
e-mail to {contact}\n",
list_name = self.name,
subscribe = subscription_policy.map_or(
Cow::Borrowed("This list is not open to subscriptions."),
|p| if p.open {
Cow::Owned(format!(
"Anyone can subscribe without restrictions. Send an e-mail to {} with the \
subject `subscribe`.",
self.request_subaddr(),
))
} else if p.manual {
Cow::Borrowed(
"The list owners must manually add you to the list of subscriptions.",
)
} else if p.request {
Cow::Owned(format!(
"Anyone can request to subscribe. Send an e-mail to {} with the subject \
`subscribe` and a confirmation will be sent to you when your request is \
approved.",
self.request_subaddr(),
))
} else {
Cow::Borrowed("Please contact the list owners for details on how to subscribe.")
}
),
post = post_policy.map_or(Cow::Borrowed("This list does not allow posting."), |p| {
if p.announce_only {
Cow::Borrowed(
"This list is announce only, which means that you can only receive posts \
from the list owners.",
)
} else if p.subscription_only {
Cow::Owned(format!(
"Only list subscriptions can post to this list. Send your post to {}",
self.address
))
} else if p.approval_needed {
Cow::Owned(format!(
"Anyone can post, but approval from list owners is required if they are \
not subscribed. Send your post to {}",
self.address
))
} else {
Cow::Borrowed("This list does not allow posting.")
}
}),
contact = self.owner_mailto().address,
)
}
}
/// A mailing list subscription entry.

View File

@ -45,6 +45,8 @@ impl std::fmt::Display for Template {
}
impl Template {
/// Template name for generic list help e-mail.
pub const GENERIC_HELP: &str = "generic-help";
/// Template name for generic failure e-mail.
pub const GENERIC_FAILURE: &str = "generic-failure";
/// Template name for generic success e-mail.
@ -187,4 +189,16 @@ impl Template {
body: "{{ details|safe if details else \"\" }}".to_string(),
}
}
/// Create a plain template for generic list help replies.
pub fn default_generic_help() -> Self {
Self {
pk: -1,
name: Self::GENERIC_HELP.to_string(),
list: None,
subject: Some("{{ subject if subject else \"Help for mailing list\" }}".to_string()),
headers_json: None,
body: "{{ details }}".to_string(),
}
}
}

View File

@ -699,7 +699,6 @@ mod tests {
"6PxWKC/OELf3gyEBRPouxsF7xSZQ==\n",
"-----END SSH SIGNATURE-----\n"
);
const NAMESPACE: &str = "doc-test@example.com";
let mut sig = SshSignature {
email: "user@example.com".to_string(),

View File

@ -58,6 +58,7 @@ impl SessionMessages for WritableSession {
ret
}
#[allow(clippy::significant_drop_tightening)]
fn add_message(&mut self, message: Message) -> Result<(), ResponseError> {
let mut messages: Vec<Message> = self.get(Message::MESSAGE_KEY).unwrap_or_default();
messages.push(message);