Skip to content

Commit 04c34f6

Browse files
committed
models/krate: Move Crate::owner_add() fn into corresponding controller
This fn performs way to much non-database stuff to be in the `models` module...
1 parent abd42d4 commit 04c34f6

File tree

2 files changed

+91
-89
lines changed

2 files changed

+91
-89
lines changed

src/controllers/krate/owners.rs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@
22
33
use crate::controllers::krate::CratePath;
44
use crate::models::krate::OwnerRemoveError;
5-
use crate::models::{krate::NewOwnerInvite, token::EndpointScope};
5+
use crate::models::{
6+
krate::NewOwnerInvite, token::EndpointScope, CrateOwner, NewCrateOwnerInvitation,
7+
NewCrateOwnerInvitationOutcome, OwnerKind,
8+
};
69
use crate::models::{Crate, Owner, Rights, Team, User};
710
use crate::util::errors::{bad_request, crate_not_found, custom, AppResult, BoxedAppError};
811
use crate::views::EncodableOwner;
9-
use crate::{app::AppState, models::krate::OwnerAddError};
12+
use crate::{app::AppState, App};
1013
use crate::{auth::AuthCheck, email::Email};
1114
use axum::Json;
1215
use axum_extra::json;
1316
use axum_extra::response::ErasedJson;
17+
use chrono::Utc;
18+
use crates_io_database::schema::crate_owners;
1419
use diesel::prelude::*;
1520
use diesel_async::scoped_futures::ScopedFutureExt;
16-
use diesel_async::{AsyncConnection, RunQueryDsl};
21+
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
1722
use http::request::Parts;
1823
use http::StatusCode;
1924
use secrecy::{ExposeSecret, SecretString};
25+
use thiserror::Error;
2026

2127
/// List crate owners.
2228
#[utoipa::path(
@@ -200,7 +206,7 @@ async fn modify_owners(
200206
return Err(bad_request(format_args!("`{login}` is already an owner")));
201207
}
202208

203-
match krate.owner_add(&app, conn, user, login).await {
209+
match add_owner(&app, conn, user, &krate, login).await {
204210
// A user was successfully invited, and they must accept
205211
// the invite, and a best-effort attempt should be made
206212
// to email them the invite token for one-click
@@ -275,6 +281,85 @@ async fn modify_owners(
275281
Ok(json!({ "msg": comma_sep_msg, "ok": true }))
276282
}
277283

284+
/// Invite `login` as an owner of this crate, returning the created
285+
/// [`NewOwnerInvite`].
286+
async fn add_owner(
287+
app: &App,
288+
conn: &mut AsyncPgConnection,
289+
req_user: &User,
290+
krate: &Crate,
291+
login: &str,
292+
) -> Result<NewOwnerInvite, OwnerAddError> {
293+
use diesel::insert_into;
294+
295+
let owner = Owner::find_or_create_by_login(app, conn, req_user, login).await?;
296+
match owner {
297+
// Users are invited and must accept before being added
298+
Owner::User(user) => {
299+
let expires_at = Utc::now() + app.config.ownership_invitations_expiration;
300+
let invite = NewCrateOwnerInvitation {
301+
invited_user_id: user.id,
302+
invited_by_user_id: req_user.id,
303+
crate_id: krate.id,
304+
expires_at,
305+
};
306+
307+
let creation_ret = invite.create(conn).await.map_err(BoxedAppError::from)?;
308+
309+
match creation_ret {
310+
NewCrateOwnerInvitationOutcome::InviteCreated { plaintext_token } => {
311+
Ok(NewOwnerInvite::User(user, plaintext_token))
312+
}
313+
NewCrateOwnerInvitationOutcome::AlreadyExists => {
314+
Err(OwnerAddError::AlreadyInvited(Box::new(user)))
315+
}
316+
}
317+
}
318+
// Teams are added as owners immediately
319+
Owner::Team(team) => {
320+
insert_into(crate_owners::table)
321+
.values(&CrateOwner {
322+
crate_id: krate.id,
323+
owner_id: team.id,
324+
created_by: req_user.id,
325+
owner_kind: OwnerKind::Team,
326+
email_notifications: true,
327+
})
328+
.on_conflict(crate_owners::table.primary_key())
329+
.do_update()
330+
.set(crate_owners::deleted.eq(false))
331+
.execute(conn)
332+
.await
333+
.map_err(BoxedAppError::from)?;
334+
335+
Ok(NewOwnerInvite::Team(team))
336+
}
337+
}
338+
}
339+
340+
/// Error results from a [`add_owner()`] model call.
341+
#[derive(Debug, Error)]
342+
enum OwnerAddError {
343+
/// An opaque [`BoxedAppError`].
344+
#[error("{0}")] // AppError does not impl Error
345+
AppError(BoxedAppError),
346+
347+
/// The requested invitee already has a pending invite.
348+
///
349+
/// Note: Teams are always immediately added, so they cannot have a pending
350+
/// invite to cause this error.
351+
#[error("user already has pending invite")]
352+
AlreadyInvited(Box<User>),
353+
}
354+
355+
/// A [`BoxedAppError`] does not impl [`std::error::Error`] so it needs a manual
356+
/// [`From`] impl.
357+
impl From<BoxedAppError> for OwnerAddError {
358+
fn from(value: BoxedAppError) -> Self {
359+
Self::AppError(value)
360+
}
361+
}
362+
278363
impl From<OwnerRemoveError> for BoxedAppError {
279364
fn from(error: OwnerRemoveError) -> Self {
280365
match error {

src/models/krate.rs

Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::{NaiveDateTime, Utc};
1+
use chrono::NaiveDateTime;
22
use diesel::associations::Identifiable;
33
use diesel::dsl;
44
use diesel::pg::Pg;
@@ -11,12 +11,8 @@ use thiserror::Error;
1111

1212
use crate::models::helpers::with_count::*;
1313
use crate::models::version::TopVersions;
14-
use crate::models::{
15-
CrateOwner, NewCrateOwnerInvitation, NewCrateOwnerInvitationOutcome, Owner, OwnerKind,
16-
ReverseDependency, User, Version,
17-
};
14+
use crate::models::{CrateOwner, Owner, OwnerKind, ReverseDependency, User, Version};
1815
use crate::schema::*;
19-
use crate::{app::App, util::errors::BoxedAppError};
2016
use crates_io_diesel_helpers::canon_crate_name;
2117

2218
use super::Team;
@@ -383,62 +379,6 @@ impl Crate {
383379
Ok(users.chain(teams).collect())
384380
}
385381

386-
/// Invite `login` as an owner of this crate, returning the created
387-
/// [`NewOwnerInvite`].
388-
pub async fn owner_add(
389-
&self,
390-
app: &App,
391-
conn: &mut AsyncPgConnection,
392-
req_user: &User,
393-
login: &str,
394-
) -> Result<NewOwnerInvite, OwnerAddError> {
395-
use diesel::insert_into;
396-
397-
let owner = Owner::find_or_create_by_login(app, conn, req_user, login).await?;
398-
match owner {
399-
// Users are invited and must accept before being added
400-
Owner::User(user) => {
401-
let expires_at = Utc::now() + app.config.ownership_invitations_expiration;
402-
let invite = NewCrateOwnerInvitation {
403-
invited_user_id: user.id,
404-
invited_by_user_id: req_user.id,
405-
crate_id: self.id,
406-
expires_at,
407-
};
408-
409-
let creation_ret = invite.create(conn).await.map_err(BoxedAppError::from)?;
410-
411-
match creation_ret {
412-
NewCrateOwnerInvitationOutcome::InviteCreated { plaintext_token } => {
413-
Ok(NewOwnerInvite::User(user, plaintext_token))
414-
}
415-
NewCrateOwnerInvitationOutcome::AlreadyExists => {
416-
Err(OwnerAddError::AlreadyInvited(Box::new(user)))
417-
}
418-
}
419-
}
420-
// Teams are added as owners immediately
421-
Owner::Team(team) => {
422-
insert_into(crate_owners::table)
423-
.values(&CrateOwner {
424-
crate_id: self.id,
425-
owner_id: team.id,
426-
created_by: req_user.id,
427-
owner_kind: OwnerKind::Team,
428-
email_notifications: true,
429-
})
430-
.on_conflict(crate_owners::table.primary_key())
431-
.do_update()
432-
.set(crate_owners::deleted.eq(false))
433-
.execute(conn)
434-
.await
435-
.map_err(BoxedAppError::from)?;
436-
437-
Ok(NewOwnerInvite::Team(team))
438-
}
439-
}
440-
}
441-
442382
pub async fn owner_remove(
443383
&self,
444384
conn: &mut AsyncPgConnection,
@@ -519,21 +459,6 @@ pub enum NewOwnerInvite {
519459
Team(Team),
520460
}
521461

522-
/// Error results from a [`Crate::owner_add()`] model call.
523-
#[derive(Debug, Error)]
524-
pub enum OwnerAddError {
525-
/// An opaque [`BoxedAppError`].
526-
#[error("{0}")] // AppError does not impl Error
527-
AppError(BoxedAppError),
528-
529-
/// The requested invitee already has a pending invite.
530-
///
531-
/// Note: Teams are always immediately added, so they cannot have a pending
532-
/// invite to cause this error.
533-
#[error("user already has pending invite")]
534-
AlreadyInvited(Box<User>),
535-
}
536-
537462
#[derive(Debug, Error)]
538463
pub enum OwnerRemoveError {
539464
#[error(transparent)]
@@ -549,14 +474,6 @@ impl OwnerRemoveError {
549474
}
550475
}
551476

552-
/// A [`BoxedAppError`] does not impl [`std::error::Error`] so it needs a manual
553-
/// [`From`] impl.
554-
impl From<BoxedAppError> for OwnerAddError {
555-
fn from(value: BoxedAppError) -> Self {
556-
Self::AppError(value)
557-
}
558-
}
559-
560477
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
561478
pub enum InvalidFeature {
562479
#[error("feature cannot be empty")]

0 commit comments

Comments
 (0)