From 8838932b407dad8bf51bc8e0807e6bcdbcaa40f6 Mon Sep 17 00:00:00 2001 From: Niklas Anderson Date: Wed, 13 Nov 2019 21:25:14 -0800 Subject: [PATCH 1/2] Send email on crate owner invitation If the recipient of a crate ownership invitation has a verified email address, they will be sent an email indicating that a specified user has invited them to become an owner of a named crate. Only sends the email on the first instance of an invitation. --- src/email.rs | 17 +++++++++++++++++ src/models/krate.rs | 23 +++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/email.rs b/src/email.rs index 0962b60f95..d312195970 100644 --- a/src/email.rs +++ b/src/email.rs @@ -86,6 +86,23 @@ https://crates.io/confirm/{}", send_email(email, subject, &body) } +/// Attempts to send a crate owner invitation email. Swallows all errors. +/// +/// Whether or not the email is sent, the invitation entry will be created in +/// the database and the user will see the invitation when they visit +/// https://crates.io/me/pending-invites/. +pub fn send_owner_invite_email(email: &str, user_name: &str, crate_name: &str) { + let subject = "Please confirm crate ownership"; + let body = format!( + "{} has invited you to become an owner of the crate {}!\n +Please visit https://crates.io/me/pending-invites to accept or reject +this invitation.", + user_name, crate_name + ); + + let _ = send_email(email, subject, &body); +} + fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> { let mailgun_config = init_config_vars(); let email = build_email(recipient, subject, body, &mailgun_config)?; diff --git a/src/models/krate.rs b/src/models/krate.rs index 6d2fa948aa..e83018dac6 100644 --- a/src/models/krate.rs +++ b/src/models/krate.rs @@ -7,11 +7,12 @@ use indexmap::IndexMap; use url::Url; use crate::app::App; +use crate::email; use crate::util::{human, CargoResult}; use crate::models::{ - Badge, Category, CrateOwner, Keyword, NewCrateOwnerInvitation, Owner, OwnerKind, - ReverseDependency, User, Version, + Badge, Category, CrateOwner, CrateOwnerInvitation, Keyword, NewCrateOwnerInvitation, Owner, + OwnerKind, ReverseDependency, User, Version, }; use crate::views::{EncodableCrate, EncodableCrateLinks}; @@ -427,14 +428,28 @@ impl Crate { match owner { // Users are invited and must accept before being added owner @ Owner::User(_) => { - insert_into(crate_owner_invitations::table) + let maybe_inserted = insert_into(crate_owner_invitations::table) .values(&NewCrateOwnerInvitation { invited_user_id: owner.id(), invited_by_user_id: req_user.id, crate_id: self.id, }) .on_conflict_do_nothing() - .execute(conn)?; + .get_result::(conn) + .optional()?; + + if maybe_inserted.is_some() { + if let Owner::User(user) = &owner { + if let Ok(Some(email)) = user.verified_email(&conn) { + email::send_owner_invite_email( + &email.as_str(), + &req_user.gh_login.as_str(), + &self.name.as_str(), + ); + } + } + } + Ok(format!( "user {} has been invited to be an owner of crate {}", owner.login(), From cef0429bbe950280e0b6b65b82ecc59ff99493aa Mon Sep 17 00:00:00 2001 From: Niklas Anderson Date: Fri, 22 Nov 2019 08:18:08 -0800 Subject: [PATCH 2/2] Adjust crate ownership invite conditional and email subject --- src/email.rs | 2 +- src/models/krate.rs | 21 +++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/email.rs b/src/email.rs index d312195970..e32ef01243 100644 --- a/src/email.rs +++ b/src/email.rs @@ -92,7 +92,7 @@ https://crates.io/confirm/{}", /// the database and the user will see the invitation when they visit /// https://crates.io/me/pending-invites/. pub fn send_owner_invite_email(email: &str, user_name: &str, crate_name: &str) { - let subject = "Please confirm crate ownership"; + let subject = "Crate ownership invitation"; let body = format!( "{} has invited you to become an owner of the crate {}!\n Please visit https://crates.io/me/pending-invites to accept or reject diff --git a/src/models/krate.rs b/src/models/krate.rs index e83018dac6..38de90b074 100644 --- a/src/models/krate.rs +++ b/src/models/krate.rs @@ -427,10 +427,10 @@ impl Crate { match owner { // Users are invited and must accept before being added - owner @ Owner::User(_) => { + Owner::User(user) => { let maybe_inserted = insert_into(crate_owner_invitations::table) .values(&NewCrateOwnerInvitation { - invited_user_id: owner.id(), + invited_user_id: user.id, invited_by_user_id: req_user.id, crate_id: self.id, }) @@ -439,21 +439,18 @@ impl Crate { .optional()?; if maybe_inserted.is_some() { - if let Owner::User(user) = &owner { - if let Ok(Some(email)) = user.verified_email(&conn) { - email::send_owner_invite_email( - &email.as_str(), - &req_user.gh_login.as_str(), - &self.name.as_str(), - ); - } + if let Ok(Some(email)) = user.verified_email(&conn) { + email::send_owner_invite_email( + &email.as_str(), + &req_user.gh_login.as_str(), + &self.name.as_str(), + ); } } Ok(format!( "user {} has been invited to be an owner of crate {}", - owner.login(), - self.name + user.gh_login, self.name )) } // Teams are added as owners immediately