Skip to content

Commit 0ba3ecc

Browse files
authored
Merge pull request #10566 from Turbo87/new-email
models/email: Improve `NewEmail` struct
2 parents 06a8430 + 3299698 commit 0ba3ecc

File tree

4 files changed

+69
-44
lines changed

4 files changed

+69
-44
lines changed

src/controllers/user/update.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::app::AppState;
22
use crate::auth::AuthCheck;
33
use crate::controllers::helpers::ok_true;
44
use crate::models::NewEmail;
5-
use crate::schema::{emails, users};
5+
use crate::schema::users;
66
use crate::util::errors::{bad_request, server_error, AppResult};
77
use axum::extract::Path;
88
use axum::response::Response;
@@ -93,21 +93,13 @@ pub async fn update_user(
9393
.parse::<Address>()
9494
.map_err(|_| bad_request("invalid email address"))?;
9595

96-
let new_email = NewEmail {
97-
user_id: user.id,
98-
email: user_email,
99-
};
96+
let new_email = NewEmail::builder()
97+
.user_id(user.id)
98+
.email(user_email)
99+
.build();
100100

101-
let token = diesel::insert_into(emails::table)
102-
.values(&new_email)
103-
.on_conflict(emails::user_id)
104-
.do_update()
105-
.set(&new_email)
106-
.returning(emails::token)
107-
.get_result::<String>(&mut conn)
108-
.await
109-
.map(SecretString::from)
110-
.map_err(|_| server_error("Error in creating token"))?;
101+
let token = new_email.insert_or_update(&mut conn).await;
102+
let token = token.map_err(|_| server_error("Error in creating token"))?;
111103

112104
// This swallows any errors that occur while attempting to send the email. Some users have
113105
// an invalid email set in their GitHub profile, and we should let them sign in even though

src/models/email.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use bon::Builder;
12
use chrono::NaiveDateTime;
3+
use diesel::{OptionalExtension, QueryResult};
4+
use diesel_async::{AsyncPgConnection, RunQueryDsl};
25
use secrecy::SecretString;
36

47
use crate::models::User;
@@ -16,9 +19,53 @@ pub struct Email {
1619
pub token_generated_at: Option<NaiveDateTime>,
1720
}
1821

19-
#[derive(Debug, Insertable, AsChangeset)]
22+
#[derive(Debug, Insertable, AsChangeset, Builder)]
2023
#[diesel(table_name = emails, check_for_backend(diesel::pg::Pg))]
2124
pub struct NewEmail<'a> {
2225
pub user_id: i32,
2326
pub email: &'a str,
27+
#[builder(default = false)]
28+
pub verified: bool,
29+
}
30+
31+
impl NewEmail<'_> {
32+
pub async fn insert(&self, conn: &mut AsyncPgConnection) -> QueryResult<()> {
33+
diesel::insert_into(emails::table)
34+
.values(self)
35+
.execute(conn)
36+
.await?;
37+
38+
Ok(())
39+
}
40+
41+
/// Inserts the email into the database and returns the confirmation token,
42+
/// or does nothing if it already exists and returns `None`.
43+
pub async fn insert_if_missing(
44+
&self,
45+
conn: &mut AsyncPgConnection,
46+
) -> QueryResult<Option<SecretString>> {
47+
diesel::insert_into(emails::table)
48+
.values(self)
49+
.on_conflict_do_nothing()
50+
.returning(emails::token)
51+
.get_result::<String>(conn)
52+
.await
53+
.map(Into::into)
54+
.optional()
55+
}
56+
57+
pub async fn insert_or_update(
58+
&self,
59+
conn: &mut AsyncPgConnection,
60+
) -> QueryResult<SecretString> {
61+
diesel::insert_into(emails::table)
62+
.values(self)
63+
.on_conflict(emails::user_id)
64+
.do_update()
65+
.set(self)
66+
.returning(emails::token)
67+
.get_result::<String>(conn)
68+
.await
69+
.map(Into::into)
70+
}
2471
}

src/models/user.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use chrono::NaiveDateTime;
22
use diesel::prelude::*;
33
use diesel_async::scoped_futures::ScopedFutureExt;
44
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
5-
use secrecy::SecretString;
65

76
use crate::app::App;
87
use crate::controllers::user::update::UserConfirmEmail;
@@ -171,21 +170,12 @@ impl<'a> NewUser<'a> {
171170

172171
// To send the user an account verification email
173172
if let Some(user_email) = email {
174-
let new_email = NewEmail {
175-
user_id: user.id,
176-
email: user_email,
177-
};
178-
179-
let token = insert_into(emails::table)
180-
.values(&new_email)
181-
.on_conflict_do_nothing()
182-
.returning(emails::token)
183-
.get_result::<String>(conn)
184-
.await
185-
.optional()?
186-
.map(SecretString::from);
187-
188-
if let Some(token) = token {
173+
let new_email = NewEmail::builder()
174+
.user_id(user.id)
175+
.email(user_email)
176+
.build();
177+
178+
if let Some(token) = new_email.insert_if_missing(conn).await? {
189179
// Swallows any error. Some users might insert an invalid email address here.
190180
let email = UserConfirmEmail {
191181
user_name: &user.gh_login,

src/tests/util/test_app.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::config::{
44
};
55
use crate::middleware::cargo_compat::StatusCodeConfig;
66
use crate::models::token::{CrateScope, EndpointScope};
7-
use crate::models::User;
7+
use crate::models::{NewEmail, User};
88
use crate::rate_limiter::{LimitedAction, RateLimiterConfig};
99
use crate::schema::users;
1010
use crate::storage::StorageConfig;
@@ -120,8 +120,6 @@ impl TestApp {
120120
///
121121
/// This method updates the database directly
122122
pub async fn db_new_user(&self, username: &str) -> MockCookieUser {
123-
use crate::schema::emails;
124-
use diesel::prelude::*;
125123
use diesel_async::RunQueryDsl;
126124

127125
let mut conn = self.db_conn().await;
@@ -134,15 +132,13 @@ impl TestApp {
134132
.await
135133
.unwrap();
136134

137-
diesel::insert_into(emails::table)
138-
.values((
139-
emails::user_id.eq(user.id),
140-
emails::email.eq(email),
141-
emails::verified.eq(true),
142-
))
143-
.execute(&mut conn)
144-
.await
145-
.unwrap();
135+
let new_email = NewEmail::builder()
136+
.user_id(user.id)
137+
.email(&email)
138+
.verified(true)
139+
.build();
140+
141+
new_email.insert(&mut conn).await.unwrap();
146142

147143
MockCookieUser {
148144
app: self.clone(),

0 commit comments

Comments
 (0)