22
33use crate :: models:: { krate:: NewOwnerInvite , token:: EndpointScope } ;
44use crate :: models:: { Crate , Owner , Rights , Team , User } ;
5- use crate :: tasks:: spawn_blocking;
65use crate :: util:: diesel:: prelude:: * ;
76use crate :: util:: errors:: { bad_request, crate_not_found, custom, AppResult } ;
87use crate :: views:: EncodableOwner ;
@@ -12,11 +11,11 @@ use axum::extract::Path;
1211use axum:: Json ;
1312use axum_extra:: json;
1413use axum_extra:: response:: ErasedJson ;
15- use diesel_async:: async_connection_wrapper:: AsyncConnectionWrapper ;
14+ use diesel_async:: scoped_futures:: ScopedFutureExt ;
15+ use diesel_async:: AsyncConnection ;
1616use http:: request:: Parts ;
1717use http:: StatusCode ;
1818use secrecy:: { ExposeSecret , SecretString } ;
19- use tokio:: runtime:: Handle ;
2019
2120/// Handles the `GET /crates/:crate_id/owners` route.
2221pub async fn owners ( state : AppState , Path ( crate_name) : Path < String > ) -> AppResult < ErasedJson > {
@@ -62,25 +61,23 @@ pub async fn owner_team(state: AppState, Path(crate_name): Path<String>) -> AppR
6261
6362/// Handles the `GET /crates/:crate_id/owner_user` route.
6463pub async fn owner_user ( state : AppState , Path ( crate_name) : Path < String > ) -> AppResult < ErasedJson > {
65- let conn = state. db_read ( ) . await ?;
66- spawn_blocking ( move || {
67- use diesel:: RunQueryDsl ;
64+ use diesel_async:: RunQueryDsl ;
6865
69- let conn : & mut AsyncConnectionWrapper < _ > = & mut conn . into ( ) ;
66+ let mut conn = state . db_read ( ) . await ? ;
7067
71- let krate: Crate = Crate :: by_name ( & crate_name)
72- . first ( conn)
73- . optional ( ) ?
74- . ok_or_else ( || crate_not_found ( & crate_name) ) ?;
68+ let krate: Crate = Crate :: by_name ( & crate_name)
69+ . first ( & mut conn)
70+ . await
71+ . optional ( ) ?
72+ . ok_or_else ( || crate_not_found ( & crate_name) ) ?;
7573
76- let owners = User :: owning ( & krate, conn) ?
77- . into_iter ( )
78- . map ( Owner :: into)
79- . collect :: < Vec < EncodableOwner > > ( ) ;
74+ let owners = User :: owning ( & krate, & mut conn)
75+ . await ?
76+ . into_iter ( )
77+ . map ( Owner :: into)
78+ . collect :: < Vec < EncodableOwner > > ( ) ;
8079
81- Ok ( json ! ( { "users" : owners } ) )
82- } )
83- . await ?
80+ Ok ( json ! ( { "users" : owners } ) )
8481}
8582
8683/// Handles the `PUT /crates/:crate_id/owners` route.
@@ -116,6 +113,8 @@ async fn modify_owners(
116113 body : ChangeOwnersRequest ,
117114 add : bool ,
118115) -> AppResult < ErasedJson > {
116+ use diesel_async:: RunQueryDsl ;
117+
119118 let logins = body. owners ;
120119
121120 // Bound the number of invites processed per request to limit the cost of
@@ -132,121 +131,124 @@ async fn modify_owners(
132131 . for_crate ( & crate_name)
133132 . check ( & parts, & mut conn)
134133 . await ?;
135- spawn_blocking ( move || {
136- use diesel:: RunQueryDsl ;
137-
138- let conn: & mut AsyncConnectionWrapper < _ > = & mut conn. into ( ) ;
139-
140- let user = auth. user ( ) ;
141-
142- // The set of emails to send out after invite processing is complete and
143- // the database transaction has committed.
144- let mut emails = Vec :: with_capacity ( logins. len ( ) ) ;
145-
146- let comma_sep_msg = conn. transaction ( |conn| {
147- let krate: Crate = Crate :: by_name ( & crate_name)
148- . first ( conn)
149- . optional ( ) ?
150- . ok_or_else ( || crate_not_found ( & crate_name) ) ?;
151-
152- let owners = krate. owners ( conn) ?;
153134
154- match Handle :: current ( ) . block_on ( user. rights ( & app, & owners) ) ? {
155- Rights :: Full => { }
156- // Yes!
157- Rights :: Publish => {
158- return Err ( custom (
159- StatusCode :: FORBIDDEN ,
160- "team members don't have permission to modify owners" ,
161- ) ) ;
162- }
163- Rights :: None => {
164- return Err ( custom (
165- StatusCode :: FORBIDDEN ,
166- "only owners have permission to modify owners" ,
167- ) ) ;
135+ let user = auth. user ( ) ;
136+
137+ let ( comma_sep_msg, emails) = conn
138+ . transaction ( |conn| {
139+ let app = app. clone ( ) ;
140+ async move {
141+ let krate: Crate = Crate :: by_name ( & crate_name)
142+ . first ( conn)
143+ . await
144+ . optional ( ) ?
145+ . ok_or_else ( || crate_not_found ( & crate_name) ) ?;
146+
147+ let owners = krate. async_owners ( conn) . await ?;
148+
149+ match user. rights ( & app, & owners) . await ? {
150+ Rights :: Full => { }
151+ // Yes!
152+ Rights :: Publish => {
153+ return Err ( custom (
154+ StatusCode :: FORBIDDEN ,
155+ "team members don't have permission to modify owners" ,
156+ ) ) ;
157+ }
158+ Rights :: None => {
159+ return Err ( custom (
160+ StatusCode :: FORBIDDEN ,
161+ "only owners have permission to modify owners" ,
162+ ) ) ;
163+ }
168164 }
169- }
170165
171- let comma_sep_msg = if add {
172- let mut msgs = Vec :: with_capacity ( logins. len ( ) ) ;
173- for login in & logins {
174- let login_test =
175- |owner : & Owner | owner. login ( ) . to_lowercase ( ) == * login. to_lowercase ( ) ;
176- if owners. iter ( ) . any ( login_test) {
177- return Err ( bad_request ( format_args ! ( "`{login}` is already an owner" ) ) ) ;
178- }
166+ // The set of emails to send out after invite processing is complete and
167+ // the database transaction has committed.
168+ let mut emails = Vec :: with_capacity ( logins. len ( ) ) ;
169+
170+ let comma_sep_msg = if add {
171+ let mut msgs = Vec :: with_capacity ( logins. len ( ) ) ;
172+ for login in & logins {
173+ let login_test =
174+ |owner : & Owner | owner. login ( ) . to_lowercase ( ) == * login. to_lowercase ( ) ;
175+ if owners. iter ( ) . any ( login_test) {
176+ return Err ( bad_request ( format_args ! ( "`{login}` is already an owner" ) ) ) ;
177+ }
179178
180- match krate. owner_add ( & app, conn, user, login) {
181- // A user was successfully invited, and they must accept
182- // the invite, and a best-effort attempt should be made
183- // to email them the invite token for one-click
184- // acceptance.
185- Ok ( NewOwnerInvite :: User ( invitee, token) ) => {
186- msgs. push ( format ! (
187- "user {} has been invited to be an owner of crate {}" ,
188- invitee. gh_login, krate. name,
189- ) ) ;
190-
191- if let Some ( recipient) = invitee. verified_email ( conn) . ok ( ) . flatten ( ) {
192- emails. push ( OwnerInviteEmail {
193- recipient_email_address : recipient,
194- inviter : user. gh_login . clone ( ) ,
195- domain : app. emails . domain . clone ( ) ,
196- crate_name : krate. name . clone ( ) ,
197- token,
198- } ) ;
179+ match krate. owner_add ( & app, conn, user, login) . await {
180+ // A user was successfully invited, and they must accept
181+ // the invite, and a best-effort attempt should be made
182+ // to email them the invite token for one-click
183+ // acceptance.
184+ Ok ( NewOwnerInvite :: User ( invitee, token) ) => {
185+ msgs. push ( format ! (
186+ "user {} has been invited to be an owner of crate {}" ,
187+ invitee. gh_login, krate. name,
188+ ) ) ;
189+
190+ if let Some ( recipient) =
191+ invitee. async_verified_email ( conn) . await . ok ( ) . flatten ( )
192+ {
193+ emails. push ( OwnerInviteEmail {
194+ recipient_email_address : recipient,
195+ inviter : user. gh_login . clone ( ) ,
196+ domain : app. emails . domain . clone ( ) ,
197+ crate_name : krate. name . clone ( ) ,
198+ token,
199+ } ) ;
200+ }
199201 }
200- }
201202
202- // A team was successfully invited. They are immediately
203- // added, and do not have an invite token.
204- Ok ( NewOwnerInvite :: Team ( team) ) => msgs. push ( format ! (
205- "team {} has been added as an owner of crate {}" ,
206- team. login, krate. name
207- ) ) ,
203+ // A team was successfully invited. They are immediately
204+ // added, and do not have an invite token.
205+ Ok ( NewOwnerInvite :: Team ( team) ) => msgs. push ( format ! (
206+ "team {} has been added as an owner of crate {}" ,
207+ team. login, krate. name
208+ ) ) ,
208209
209- // This user has a pending invite.
210- Err ( OwnerAddError :: AlreadyInvited ( user) ) => msgs. push ( format ! (
210+ // This user has a pending invite.
211+ Err ( OwnerAddError :: AlreadyInvited ( user) ) => msgs. push ( format ! (
211212 "user {} already has a pending invitation to be an owner of crate {}" ,
212213 user. gh_login, krate. name
213214 ) ) ,
214215
215- // An opaque error occurred.
216- Err ( OwnerAddError :: AppError ( e) ) => return Err ( e) ,
216+ // An opaque error occurred.
217+ Err ( OwnerAddError :: AppError ( e) ) => return Err ( e) ,
218+ }
217219 }
218- }
219- msgs. join ( "," )
220- } else {
221- for login in & logins {
222- krate. owner_remove ( conn, login) ?;
223- }
224- if User :: owning ( & krate, conn) ?. is_empty ( ) {
225- return Err ( bad_request (
226- "cannot remove all individual owners of a crate. \
220+ msgs. join ( "," )
221+ } else {
222+ for login in & logins {
223+ krate. owner_remove ( conn, login) . await ?;
224+ }
225+ if User :: owning ( & krate, conn) . await ?. is_empty ( ) {
226+ return Err ( bad_request (
227+ "cannot remove all individual owners of a crate. \
227228 Team member don't have permission to modify owners, so \
228229 at least one individual owner is required.",
229- ) ) ;
230- }
231- "owners successfully removed" . to_owned ( )
232- } ;
230+ ) ) ;
231+ }
232+ "owners successfully removed" . to_owned ( )
233+ } ;
233234
234- Ok ( comma_sep_msg)
235- } ) ?;
235+ Ok ( ( comma_sep_msg, emails) )
236+ }
237+ . scope_boxed ( )
238+ } )
239+ . await ?;
236240
237- // Send the accumulated invite emails now the database state has
238- // committed.
239- for email in emails {
240- let addr = email. recipient_email_address ( ) . to_string ( ) ;
241+ // Send the accumulated invite emails now the database state has
242+ // committed.
243+ for email in emails {
244+ let addr = email. recipient_email_address ( ) . to_string ( ) ;
241245
242- if let Err ( e) = app. emails . send ( & addr, email) {
243- warn ! ( "Failed to send co-owner invite email: {e}" ) ;
244- }
246+ if let Err ( e) = app. emails . async_send ( & addr, email) . await {
247+ warn ! ( "Failed to send co-owner invite email: {e}" ) ;
245248 }
249+ }
246250
247- Ok ( json ! ( { "msg" : comma_sep_msg, "ok" : true } ) )
248- } )
249- . await ?
251+ Ok ( json ! ( { "msg" : comma_sep_msg, "ok" : true } ) )
250252}
251253
252254pub struct OwnerInviteEmail {
0 commit comments