Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 24 additions & 30 deletions src/controllers/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,47 +284,41 @@ pub async fn handle_invite(state: AppState, req: BytesRequest) -> AppResult<Json
let crate_invite = crate_invite.crate_owner_invite;

let mut conn = state.db_write().await?;
let auth = AuthCheck::default().check(&parts, &mut conn).await?;
spawn_blocking(move || {
let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();
let user_id = AuthCheck::default()
.check(&parts, &mut conn)
.await?
.user_id();
let invitation =
CrateOwnerInvitation::find_by_id(user_id, crate_invite.crate_id, &mut conn).await?;

let user_id = auth.user_id();

let config = &state.config;
let config = &state.config;

let invitation = CrateOwnerInvitation::find_by_id(user_id, crate_invite.crate_id, conn)?;
if crate_invite.accepted {
invitation.accept(conn, config)?;
} else {
invitation.decline(conn)?;
}
if crate_invite.accepted {
invitation.accept(&mut conn, config).await?;
} else {
invitation.decline(&mut conn).await?;
}

Ok(Json(json!({ "crate_owner_invitation": crate_invite })))
})
.await
Ok(Json(json!({ "crate_owner_invitation": crate_invite })))
}

/// Handles the `PUT /api/v1/me/crate_owner_invitations/accept/:token` route.
pub async fn handle_invite_with_token(
state: AppState,
Path(token): Path<String>,
) -> AppResult<Json<Value>> {
let conn = state.db_write().await?;
spawn_blocking(move || {
let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();
let mut conn = state.db_write().await?;
let invitation = CrateOwnerInvitation::find_by_token(&token, &mut conn).await?;

let config = &state.config;
let config = &state.config;

let invitation = CrateOwnerInvitation::find_by_token(&token, conn)?;
let crate_id = invitation.crate_id;
invitation.accept(conn, config)?;
let crate_id = invitation.crate_id;
invitation.accept(&mut conn, config).await?;

Ok(Json(json!({
"crate_owner_invitation": {
"crate_id": crate_id,
"accepted": true,
},
})))
})
.await
Ok(Json(json!({
"crate_owner_invitation": {
"crate_id": crate_id,
"accepted": true,
},
})))
}
70 changes: 44 additions & 26 deletions src/models/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::{NaiveDateTime, Utc};
use diesel_async::AsyncPgConnection;
use http::StatusCode;
use secrecy::SecretString;

Expand Down Expand Up @@ -87,30 +88,42 @@ impl CrateOwnerInvitation {
})
}

pub fn find_by_id(user_id: i32, crate_id: i32, conn: &mut impl Conn) -> QueryResult<Self> {
use diesel::RunQueryDsl;
pub async fn find_by_id(
user_id: i32,
crate_id: i32,
conn: &mut AsyncPgConnection,
) -> QueryResult<Self> {
use diesel_async::RunQueryDsl;

crate_owner_invitations::table
.find((user_id, crate_id))
.first::<Self>(conn)
.await
}

pub fn find_by_token(token: &str, conn: &mut impl Conn) -> QueryResult<Self> {
use diesel::RunQueryDsl;
pub async fn find_by_token(token: &str, conn: &mut AsyncPgConnection) -> QueryResult<Self> {
use diesel_async::RunQueryDsl;

crate_owner_invitations::table
.filter(crate_owner_invitations::token.eq(token))
.first::<Self>(conn)
.await
}

pub fn accept(self, conn: &mut impl Conn, config: &config::Server) -> AppResult<()> {
use diesel::RunQueryDsl;
pub async fn accept(
self,
conn: &mut AsyncPgConnection,
config: &config::Server,
) -> AppResult<()> {
use diesel_async::scoped_futures::ScopedFutureExt;
use diesel_async::{AsyncConnection, RunQueryDsl};

if self.is_expired(config) {
let crate_name: String = crates::table
.find(self.crate_id)
.select(crates::name)
.first(conn)?;
.first(conn)
.await?;

let detail = format!(
"The invitation to become an owner of the {crate_name} crate expired. \
Expand All @@ -121,33 +134,38 @@ impl CrateOwnerInvitation {
}

conn.transaction(|conn| {
diesel::insert_into(crate_owners::table)
.values(&CrateOwner {
crate_id: self.crate_id,
owner_id: self.invited_user_id,
created_by: self.invited_by_user_id,
owner_kind: OwnerKind::User,
email_notifications: true,
})
.on_conflict(crate_owners::table.primary_key())
.do_update()
.set(crate_owners::deleted.eq(false))
.execute(conn)?;

diesel::delete(&self).execute(conn)?;

Ok(())
async move {
diesel::insert_into(crate_owners::table)
.values(&CrateOwner {
crate_id: self.crate_id,
owner_id: self.invited_user_id,
created_by: self.invited_by_user_id,
owner_kind: OwnerKind::User,
email_notifications: true,
})
.on_conflict(crate_owners::table.primary_key())
.do_update()
.set(crate_owners::deleted.eq(false))
.execute(conn)
.await?;

diesel::delete(&self).execute(conn).await?;

Ok(())
}
.scope_boxed()
})
.await
}

pub fn decline(self, conn: &mut impl Conn) -> QueryResult<()> {
use diesel::RunQueryDsl;
pub async fn decline(self, conn: &mut AsyncPgConnection) -> QueryResult<()> {
use diesel_async::RunQueryDsl;

// The check to prevent declining expired invitations is *explicitly* missing. We do not
// care if an expired invitation is declined, as that just removes the invitation from the
// database.

diesel::delete(&self).execute(conn)?;
diesel::delete(&self).execute(conn).await?;
Ok(())
}

Expand Down