-
Notifications
You must be signed in to change notification settings - Fork 645
Drop Email column (merge and deploy the fix for #1888 first!) #1891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
521beb7
5ef002d
3544b91
fc01ad4
3be2b55
59f7169
8a8a1d3
2410f44
c7ac5cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
CREATE FUNCTION update_categories_crates_cnt() RETURNS trigger AS $$ BEGIN IF (TG_OP = 'INSERT') THEN UPDATE categories SET crates_cnt = crates_cnt + 1 WHERE id = NEW.category_id; return NEW; ELSIF (TG_OP = 'DELETE') THEN UPDATE categories SET crates_cnt = crates_cnt - 1 WHERE id = OLD.category_id; return OLD; END IF; END $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_update_categories_crates_cnt BEFORE INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE update_categories_crates_cnt(); CREATE TRIGGER touch_crate_on_modify_categories AFTER INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE touch_crate(); | ||
CREATE FUNCTION update_categories_crates_cnt() RETURNS trigger AS $$ BEGIN IF (TG_OP = 'INSERT') THEN UPDATE categories SET crates_cnt = crates_cnt + 1 WHERE id = NEW.category_id; return NEW; ELSIF (TG_OP = 'DELETE') THEN UPDATE categories SET crates_cnt = crates_cnt - 1 WHERE id = OLD.category_id; return OLD; END IF; END $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_update_categories_crates_cnt BEFORE INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE update_categories_crates_cnt(); CREATE TRIGGER touch_crate_on_modify_categories AFTER INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE touch_crate(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-- Not reversible |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
-- Your SQL goes here | ||
ALTER TABLE users DROP COLUMN email CASCADE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is super picky, but can you remove the extra space between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,10 +38,10 @@ pub fn me(req: &mut dyn Request) -> CargoResult<Response> { | |
|
||
let verified = verified.unwrap_or(false); | ||
let verification_sent = verified || verification_sent; | ||
let user = User { email, ..user }; | ||
let user = User { ..user }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this line can be completely removed now, actually! I think |
||
|
||
Ok(req.json(&EncodableMe { | ||
user: user.encodable_private(verified, verification_sent), | ||
user: user.encodable_private(email, verified, verification_sent)?, | ||
})) | ||
} | ||
|
||
|
@@ -91,8 +91,7 @@ pub fn updates(req: &mut dyn Request) -> CargoResult<Response> { | |
/// Handles the `PUT /user/:user_id` route. | ||
pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> { | ||
use self::emails::user_id; | ||
use self::users::dsl::{email, gh_login, users}; | ||
use diesel::{insert_into, update}; | ||
use diesel::insert_into; | ||
|
||
let mut body = String::new(); | ||
req.body().read_to_string(&mut body)?; | ||
|
@@ -130,10 +129,6 @@ pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> { | |
} | ||
|
||
conn.transaction::<_, Box<dyn CargoError>, _>(|| { | ||
update(users.filter(gh_login.eq(&user.gh_login))) | ||
.set(email.eq(user_email)) | ||
.execute(&*conn)?; | ||
|
||
let new_email = NewEmail { | ||
user_id: user.id, | ||
email: user_email, | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -13,20 +13,19 @@ use crate::views::{EncodablePrivateUser, EncodablePublicUser}; | |||
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset, Associations)] | ||||
pub struct User { | ||||
pub id: i32, | ||||
pub email: Option<String>, | ||||
pub gh_access_token: String, | ||||
pub gh_login: String, | ||||
pub name: Option<String>, | ||||
pub gh_avatar: Option<String>, | ||||
pub gh_id: i32, | ||||
} | ||||
|
||||
/// Represents a new user record insertible to the `users` table | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small fix: can you change |
||||
#[derive(Insertable, Debug, Default)] | ||||
#[table_name = "users"] | ||||
pub struct NewUser<'a> { | ||||
pub gh_id: i32, | ||||
pub gh_login: &'a str, | ||||
pub email: Option<&'a str>, | ||||
pub name: Option<&'a str>, | ||||
pub gh_avatar: Option<&'a str>, | ||||
pub gh_access_token: Cow<'a, str>, | ||||
|
@@ -36,23 +35,26 @@ impl<'a> NewUser<'a> { | |||
pub fn new( | ||||
gh_id: i32, | ||||
gh_login: &'a str, | ||||
email: Option<&'a str>, | ||||
name: Option<&'a str>, | ||||
gh_avatar: Option<&'a str>, | ||||
gh_access_token: &'a str, | ||||
) -> Self { | ||||
NewUser { | ||||
gh_id, | ||||
gh_login, | ||||
email, | ||||
name, | ||||
gh_avatar, | ||||
gh_access_token: Cow::Borrowed(gh_access_token), | ||||
} | ||||
} | ||||
|
||||
/// Inserts the user into the database, or updates an existing one. | ||||
pub fn create_or_update(&self, conn: &PgConnection) -> QueryResult<User> { | ||||
pub fn create_or_update( | ||||
&self, | ||||
email: Option<&'a str>, | ||||
conn: &PgConnection, | ||||
) -> QueryResult<User> { | ||||
use crate::schema::emails::columns::user_id; | ||||
use crate::schema::users::dsl::*; | ||||
use diesel::dsl::sql; | ||||
use diesel::insert_into; | ||||
|
@@ -81,15 +83,17 @@ impl<'a> NewUser<'a> { | |||
.get_result::<User>(conn)?; | ||||
|
||||
// To send the user an account verification email... | ||||
if let Some(user_email) = user.email.as_ref() { | ||||
if let Some(user_email) = email { | ||||
let new_email = NewEmail { | ||||
user_id: user.id, | ||||
email: user_email, | ||||
}; | ||||
|
||||
let token = insert_into(emails::table) | ||||
.values(&new_email) | ||||
.on_conflict_do_nothing() | ||||
.on_conflict(user_id) | ||||
.do_update() | ||||
.set(&new_email) | ||||
.returning(emails::token) | ||||
.get_result::<String>(conn) | ||||
.optional()?; | ||||
|
@@ -168,6 +172,8 @@ impl User { | |||
Ok(best) | ||||
} | ||||
|
||||
/// Queries the database for the verified emails | ||||
/// belonging to a given user | ||||
pub fn verified_email(&self, conn: &PgConnection) -> CargoResult<Option<String>> { | ||||
Ok(Email::belonging_to(self) | ||||
.select(emails::email) | ||||
|
@@ -179,19 +185,21 @@ impl User { | |||
/// Converts this `User` model into an `EncodablePrivateUser` for JSON serialization. | ||||
pub fn encodable_private( | ||||
self, | ||||
email: Option<String>, | ||||
|
||||
email_verified: bool, | ||||
email_verification_sent: bool, | ||||
) -> EncodablePrivateUser { | ||||
) -> CargoResult<EncodablePrivateUser> { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why this function needed to be changed to return a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't necessary. I was previously reading this Line 86 in 3544b91
variant result before realizing that was already "done" and the email could simply be passed to the |
||||
let User { | ||||
id, | ||||
email, | ||||
name, | ||||
gh_login, | ||||
gh_avatar, | ||||
.. | ||||
} = self; | ||||
let url = format!("https://github.com/{}", gh_login); | ||||
EncodablePrivateUser { | ||||
|
||||
Ok(EncodablePrivateUser { | ||||
id, | ||||
email, | ||||
email_verified, | ||||
|
@@ -200,7 +208,15 @@ impl User { | |||
login: gh_login, | ||||
name, | ||||
url: Some(url), | ||||
} | ||||
}) | ||||
} | ||||
|
||||
/// Queries for the email belonging to a particular user | ||||
pub fn email(&self, conn: &PgConnection) -> CargoResult<Option<String>> { | ||||
Ok(Email::belonging_to(self) | ||||
.select(emails::email) | ||||
.first::<String>(&*conn) | ||||
.optional()?) | ||||
} | ||||
|
||||
/// Converts this`User` model into an `EncodablePublicUser` for JSON serialization. | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,7 +110,7 @@ fn me() { | |
let user = app.db_new_user("foo"); | ||
let json = user.show_me(); | ||
|
||
assert_eq!(json.user.email, user.as_model().email); | ||
assert_eq!(json.user.name, user.as_model().name); | ||
} | ||
|
||
#[test] | ||
|
@@ -141,21 +141,19 @@ fn show_latest_user_case_insensitively() { | |
t!(NewUser::new( | ||
1, | ||
"foobar", | ||
Some("[email protected]"), | ||
Some("I was first then deleted my github account"), | ||
None, | ||
"bar" | ||
) | ||
.create_or_update(conn)); | ||
.create_or_update(None, conn)); | ||
t!(NewUser::new( | ||
2, | ||
"FOOBAR", | ||
Some("[email protected]"), | ||
Some("I was second, I took the foobar username on github"), | ||
None, | ||
"bar" | ||
) | ||
.create_or_update(conn)); | ||
.create_or_update(None, conn)); | ||
}); | ||
|
||
let json: UserShowPublicResponse = anon.get("api/v1/users/fOObAr").good(); | ||
|
@@ -324,7 +322,7 @@ fn updating_existing_user_doesnt_change_api_token() { | |
|
||
let user = app.db(|conn| { | ||
// Reuse gh_id but use new gh_login and gh_access_token | ||
t!(NewUser::new(gh_id, "bar", None, None, None, "bar_token").create_or_update(conn)); | ||
t!(NewUser::new(gh_id, "bar", None, None, "bar_token").create_or_update(None, conn)); | ||
|
||
// Use the original API token to find the now updated user | ||
t!(User::find_by_api_token(conn, token)) | ||
|
@@ -353,7 +351,7 @@ fn github_without_email_does_not_overwrite_email() { | |
// Don't use app.db_new_user because it adds a verified email. | ||
let user_without_github_email = app.db(|conn| { | ||
let u = new_user("arbitrary_username"); | ||
let u = u.create_or_update(conn).unwrap(); | ||
let u = u.create_or_update(None, conn).unwrap(); | ||
MockCookieUser::new(&app, u) | ||
}); | ||
let user_without_github_email_model = user_without_github_email.as_model(); | ||
|
@@ -373,7 +371,7 @@ fn github_without_email_does_not_overwrite_email() { | |
// new_user uses a None email; the rest of the fields are arbitrary | ||
..new_user("arbitrary_username") | ||
}; | ||
let u = u.create_or_update(conn).unwrap(); | ||
let u = u.create_or_update(None, conn).unwrap(); | ||
MockCookieUser::new(&app, u) | ||
}); | ||
|
||
|
@@ -386,9 +384,16 @@ fn github_without_email_does_not_overwrite_email() { | |
*/ | ||
#[test] | ||
fn github_with_email_does_not_overwrite_email() { | ||
use cargo_registry::schema::emails; | ||
|
||
let (app, _, user) = TestApp::init().with_user(); | ||
let model = user.as_model(); | ||
let original_email = &model.email; | ||
let original_email = app.db(|conn| { | ||
Email::belonging_to(model) | ||
.select(emails::email) | ||
.first::<String>(&*conn) | ||
.unwrap() | ||
}); | ||
|
||
let new_github_email = "[email protected]"; | ||
|
||
|
@@ -397,16 +402,15 @@ fn github_with_email_does_not_overwrite_email() { | |
let u = NewUser { | ||
// Use the same github ID to link to the existing account | ||
gh_id: model.gh_id, | ||
email: Some(new_github_email), | ||
// the rest of the fields are arbitrary | ||
..new_user("arbitrary_username") | ||
}; | ||
let u = u.create_or_update(conn).unwrap(); | ||
let u = u.create_or_update(Some(new_github_email), conn).unwrap(); | ||
MockCookieUser::new(&app, u) | ||
}); | ||
|
||
let json = user_with_different_email_in_github.show_me(); | ||
assert_eq!(json.user.email, *original_email); | ||
assert_eq!(json.user.email, Some(original_email)); | ||
} | ||
|
||
/* Given a crates.io user, check that the user's email can be | ||
|
@@ -512,10 +516,9 @@ fn test_confirm_user_email() { | |
// email directly into the database and we want to test the verification flow here. | ||
let user = app.db(|conn| { | ||
let u = NewUser { | ||
email: Some("[email protected]"), | ||
..new_user("arbitrary_username") | ||
}; | ||
let u = u.create_or_update(conn).unwrap(); | ||
let u = u.create_or_update(None, conn).unwrap(); | ||
MockCookieUser::new(&app, u) | ||
}); | ||
let user_model = user.as_model(); | ||
|
@@ -551,10 +554,9 @@ fn test_existing_user_email() { | |
// email directly into the database and we want to test the verification flow here. | ||
let user = app.db(|conn| { | ||
let u = NewUser { | ||
email: Some("[email protected]"), | ||
..new_user("arbitrary_username") | ||
}; | ||
let u = u.create_or_update(conn).unwrap(); | ||
let u = u.create_or_update(None, conn).unwrap(); | ||
update(Email::belonging_to(&u)) | ||
// Users created before we added verification will have | ||
// `NULL` in the `token_generated_at` column. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,9 +135,10 @@ impl TestApp { | |
use diesel::prelude::*; | ||
|
||
let user = self.db(|conn| { | ||
let mut user = crate::new_user(username).create_or_update(conn).unwrap(); | ||
let email = "[email protected]"; | ||
user.email = Some(email.to_string()); | ||
let email = "[email protected]"; | ||
let user = crate::new_user(username) | ||
.create_or_update(Some(email), conn) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha, so the unique constraint that's causing the tests to fail is that here in the test setup you're creating a new user with an email set, then lines 142-149 right after this the test setup is inserting the email into the table directly, which violates the constraint we have that each user may only have one email. To fix this, you can pass |
||
.unwrap(); | ||
diesel::insert_into(emails::table) | ||
.values(( | ||
emails::user_id.eq(user.id), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this comment :)