Skip to content

Commit a5694a8

Browse files
authored
Merge pull request #10554 from Turbo87/invite-expires-at-2
Simplify `crate_owner_invitations` expiry checks
2 parents 847ca22 + 76f39ae commit a5694a8

File tree

7 files changed

+25
-54
lines changed

7 files changed

+25
-54
lines changed

crates/crates_io_database/src/schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ diesel::table! {
222222
/// (Automatically generated by Diesel.)
223223
token -> Text,
224224
/// Point in time at which the invitation expires/expired.
225-
expires_at -> Nullable<Timestamptz>,
225+
expires_at -> Timestamptz,
226226
}
227227
}
228228

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alter table crate_owner_invitations alter column expires_at drop not null;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alter table crate_owner_invitations alter column expires_at set not null;

src/controllers/crate_owner_invitation.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ async fn prepare_list(
265265
}
266266

267267
// Turn `CrateOwnerInvitation`s into `EncodablePrivateCrateOwnerInvitation`.
268-
let config = &state.config;
269268
let mut invitations = Vec::new();
270269
let mut users_in_response = HashSet::new();
271270
for invitation in raw_invitations.into_iter() {
@@ -278,7 +277,7 @@ async fn prepare_list(
278277
.ok_or_else(|| internal(format!("missing crate with id {}", invitation.crate_id)))?
279278
.clone(),
280279
created_at: invitation.created_at,
281-
expires_at: invitation.expires_at(config),
280+
expires_at: invitation.expires_at.naive_utc(),
282281
});
283282
users_in_response.insert(invitation.invited_user_id);
284283
users_in_response.insert(invitation.invited_by_user_id);
@@ -342,10 +341,8 @@ pub async fn handle_crate_owner_invitation(
342341
let invitation =
343342
CrateOwnerInvitation::find_by_id(user_id, crate_invite.crate_id, &mut conn).await?;
344343

345-
let config = &state.config;
346-
347344
if crate_invite.accepted {
348-
invitation.accept(&mut conn, config).await?;
345+
invitation.accept(&mut conn).await?;
349346
} else {
350347
invitation.decline(&mut conn).await?;
351348
}
@@ -370,10 +367,8 @@ pub async fn accept_crate_owner_invitation_with_token(
370367
let mut conn = state.db_write().await?;
371368
let invitation = CrateOwnerInvitation::find_by_token(&token, &mut conn).await?;
372369

373-
let config = &state.config;
374-
375370
let crate_id = invitation.crate_id;
376-
invitation.accept(&mut conn, config).await?;
371+
invitation.accept(&mut conn).await?;
377372

378373
Ok(json!({
379374
"crate_owner_invitation": {

src/models/crate_owner_invitation.rs

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use diesel_async::scoped_futures::ScopedFutureExt;
44
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
55
use secrecy::SecretString;
66

7-
use crate::config;
87
use crate::models::CrateOwner;
98
use crate::schema::{crate_owner_invitations, crate_owners, crates};
109

@@ -27,32 +26,16 @@ impl NewCrateOwnerInvitation {
2726
pub async fn create(
2827
&self,
2928
conn: &mut AsyncPgConnection,
30-
config: &config::Server,
3129
) -> QueryResult<NewCrateOwnerInvitationOutcome> {
3230
// Before actually creating the invite, check if an expired invitation already exists
3331
// and delete it from the database. This allows obtaining a new invite if the old one
3432
// expired, instead of returning "already exists".
35-
conn.transaction(|conn| {
36-
async move {
37-
// This does a SELECT FOR UPDATE + DELETE instead of a DELETE with a WHERE clause to
38-
// use the model's `is_expired` method, centralizing our expiration checking logic.
39-
let existing: Option<CrateOwnerInvitation> = crate_owner_invitations::table
40-
.find((self.invited_user_id, self.crate_id))
41-
.for_update()
42-
.first(conn)
43-
.await
44-
.optional()?;
45-
46-
if let Some(existing) = existing {
47-
if existing.is_expired(config) {
48-
diesel::delete(&existing).execute(conn).await?;
49-
}
50-
}
51-
QueryResult::Ok(())
52-
}
53-
.scope_boxed()
54-
})
55-
.await?;
33+
diesel::delete(crate_owner_invitations::table)
34+
.filter(crate_owner_invitations::invited_user_id.eq(self.invited_user_id))
35+
.filter(crate_owner_invitations::crate_id.eq(self.crate_id))
36+
.filter(crate_owner_invitations::expires_at.le(Utc::now()))
37+
.execute(conn)
38+
.await?;
5639

5740
let res: Option<CrateOwnerInvitation> = diesel::insert_into(crate_owner_invitations::table)
5841
.values(self)
@@ -83,7 +66,7 @@ pub struct CrateOwnerInvitation {
8366
pub created_at: NaiveDateTime,
8467
#[diesel(deserialize_as = String)]
8568
pub token: SecretString,
86-
pub expires_at: Option<DateTime<Utc>>,
69+
pub expires_at: DateTime<Utc>,
8770
}
8871

8972
impl CrateOwnerInvitation {
@@ -105,15 +88,8 @@ impl CrateOwnerInvitation {
10588
.await
10689
}
10790

108-
pub async fn accept(
109-
self,
110-
conn: &mut AsyncPgConnection,
111-
config: &config::Server,
112-
) -> Result<(), AcceptError> {
113-
use diesel_async::scoped_futures::ScopedFutureExt;
114-
use diesel_async::{AsyncConnection, RunQueryDsl};
115-
116-
if self.is_expired(config) {
91+
pub async fn accept(self, conn: &mut AsyncPgConnection) -> Result<(), AcceptError> {
92+
if self.is_expired() {
11793
let crate_name: String = crates::table
11894
.find(self.crate_id)
11995
.select(crates::name)
@@ -151,12 +127,8 @@ impl CrateOwnerInvitation {
151127
Ok(())
152128
}
153129

154-
pub fn is_expired(&self, config: &config::Server) -> bool {
155-
self.expires_at(config) <= Utc::now().naive_utc()
156-
}
157-
158-
pub fn expires_at(&self, config: &config::Server) -> NaiveDateTime {
159-
self.created_at + config.ownership_invitations_expiration
130+
pub fn is_expired(&self) -> bool {
131+
self.expires_at <= Utc::now()
160132
}
161133
}
162134

src/models/krate.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,7 @@ impl Crate {
408408
expires_at,
409409
};
410410

411-
let creation_ret = invite
412-
.create(conn, &app.config)
413-
.await
414-
.map_err(BoxedAppError::from)?;
411+
let creation_ret = invite.create(conn).await.map_err(BoxedAppError::from)?;
415412

416413
match creation_ret {
417414
NewCrateOwnerInvitationOutcome::InviteCreated { plaintext_token } => {

src/tests/owners.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,10 +626,15 @@ pub async fn expire_invitation(app: &TestApp, crate_id: i32) {
626626
let mut conn = app.db_conn().await;
627627

628628
let expiration = app.as_inner().config.ownership_invitations_expiration;
629-
let created_at = (Utc::now() - expiration).naive_utc();
629+
630+
let now = Utc::now();
631+
let created_at = (now - expiration).naive_utc();
630632

631633
diesel::update(crate_owner_invitations::table)
632-
.set(crate_owner_invitations::created_at.eq(created_at))
634+
.set((
635+
crate_owner_invitations::created_at.eq(created_at),
636+
crate_owner_invitations::expires_at.eq(now),
637+
))
633638
.filter(crate_owner_invitations::crate_id.eq(crate_id))
634639
.execute(&mut conn)
635640
.await

0 commit comments

Comments
 (0)