Skip to content

Commit

Permalink
Auto merge of #1909 - nkanderson:1360_email_invitation, r=carols10cents
Browse files Browse the repository at this point in the history
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.

Completes the first part of #1360.
  • Loading branch information
bors committed Nov 25, 2019
2 parents 1c98d07 + cef0429 commit 4df7001
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 8 deletions.
17 changes: 17 additions & 0 deletions src/email.rs
Expand Up @@ -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 = "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
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)?;
Expand Down
28 changes: 20 additions & 8 deletions src/models/krate.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -426,19 +427,30 @@ impl Crate {

match owner {
// Users are invited and must accept before being added
owner @ Owner::User(_) => {
insert_into(crate_owner_invitations::table)
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,
})
.on_conflict_do_nothing()
.execute(conn)?;
.get_result::<CrateOwnerInvitation>(conn)
.optional()?;

if maybe_inserted.is_some() {
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
Expand Down

0 comments on commit 4df7001

Please sign in to comment.