Skip to content
Merged
32 changes: 20 additions & 12 deletions src/controllers/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ use crate::views::{
};
use axum::Json;
use axum::extract::{FromRequestParts, Path, Query};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use chrono::Utc;
use diesel::pg::Pg;
use diesel::prelude::*;
Expand Down Expand Up @@ -329,6 +327,12 @@ pub struct OwnerInvitation {
crate_owner_invite: InvitationResponse,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct HandleResponse {
#[schema(inline)]
crate_owner_invitation: InvitationResponse,
}

/// Accept or decline a crate owner invitation.
#[utoipa::path(
put,
Expand All @@ -341,13 +345,13 @@ pub struct OwnerInvitation {
("cookie" = []),
),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(HandleResponse))),
)]
pub async fn handle_crate_owner_invitation(
state: AppState,
parts: Parts,
Json(crate_invite): Json<OwnerInvitation>,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<HandleResponse>> {
let crate_invite = crate_invite.crate_owner_invite;

let mut conn = state.db_write().await?;
Expand All @@ -364,7 +368,9 @@ pub async fn handle_crate_owner_invitation(
invitation.decline(&mut conn).await?;
}

Ok(json!({ "crate_owner_invitation": crate_invite }))
Ok(Json(HandleResponse {
crate_owner_invitation: crate_invite,
}))
}

/// Accept a crate owner invitation with a token.
Expand All @@ -375,23 +381,25 @@ pub async fn handle_crate_owner_invitation(
("token" = String, Path, description = "Secret token sent to the user's email address"),
),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(HandleResponse))),
)]
pub async fn accept_crate_owner_invitation_with_token(
state: AppState,
Path(token): Path<String>,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<HandleResponse>> {
let mut conn = state.db_write().await?;
let invitation = CrateOwnerInvitation::find_by_token(&token, &mut conn).await?;

let crate_id = invitation.crate_id;
invitation.accept(&mut conn).await?;

Ok(json!({
"crate_owner_invitation": {
"crate_id": crate_id,
"accepted": true,
},
let crate_owner_invitation = InvitationResponse {
crate_id,
accepted: true,
};

Ok(Json(HandleResponse {
crate_owner_invitation,
}))
}

Expand Down
60 changes: 39 additions & 21 deletions src/controllers/krate/owners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ use crate::views::EncodableOwner;
use crate::{App, app::AppState};
use crate::{auth::AuthCheck, email::Email};
use axum::Json;
use axum_extra::json;
use axum_extra::response::ErasedJson;
use chrono::Utc;
use crates_io_github::{GitHubClient, GitHubError};
use diesel::prelude::*;
Expand All @@ -26,27 +24,37 @@ use oauth2::AccessToken;
use secrecy::{ExposeSecret, SecretString};
use thiserror::Error;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct UsersResponse {
pub users: Vec<EncodableOwner>,
}

/// List crate owners.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/owners",
params(CratePath),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(UsersResponse))),
)]
pub async fn list_owners(state: AppState, path: CratePath) -> AppResult<ErasedJson> {
pub async fn list_owners(state: AppState, path: CratePath) -> AppResult<Json<UsersResponse>> {
let mut conn = state.db_read().await?;

let krate = path.load_crate(&mut conn).await?;

let owners = krate
let users = krate
.owners(&mut conn)
.await?
.into_iter()
.map(Owner::into)
.collect::<Vec<EncodableOwner>>();

Ok(json!({ "users": owners }))
Ok(Json(UsersResponse { users }))
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct TeamsResponse {
pub teams: Vec<EncodableOwner>,
}

/// List team owners of a crate.
Expand All @@ -55,19 +63,19 @@ pub async fn list_owners(state: AppState, path: CratePath) -> AppResult<ErasedJs
path = "/api/v1/crates/{name}/owner_team",
params(CratePath),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(TeamsResponse))),
)]
pub async fn get_team_owners(state: AppState, path: CratePath) -> AppResult<ErasedJson> {
pub async fn get_team_owners(state: AppState, path: CratePath) -> AppResult<Json<TeamsResponse>> {
let mut conn = state.db_read().await?;
let krate = path.load_crate(&mut conn).await?;

let owners = Team::owning(&krate, &mut conn)
let teams = Team::owning(&krate, &mut conn)
.await?
.into_iter()
.map(Owner::into)
.collect::<Vec<EncodableOwner>>();

Ok(json!({ "teams": owners }))
Ok(Json(TeamsResponse { teams }))
}

/// List user owners of a crate.
Expand All @@ -76,20 +84,30 @@ pub async fn get_team_owners(state: AppState, path: CratePath) -> AppResult<Eras
path = "/api/v1/crates/{name}/owner_user",
params(CratePath),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(UsersResponse))),
)]
pub async fn get_user_owners(state: AppState, path: CratePath) -> AppResult<ErasedJson> {
pub async fn get_user_owners(state: AppState, path: CratePath) -> AppResult<Json<UsersResponse>> {
let mut conn = state.db_read().await?;

let krate = path.load_crate(&mut conn).await?;

let owners = User::owning(&krate, &mut conn)
let users = User::owning(&krate, &mut conn)
.await?
.into_iter()
.map(Owner::into)
.collect::<Vec<EncodableOwner>>();

Ok(json!({ "users": owners }))
Ok(Json(UsersResponse { users }))
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct ModifyResponse {
/// A message describing the result of the operation.
#[schema(example = "user ghost has been invited to be an owner of crate serde")]
pub msg: String,

#[schema(example = true)]
pub ok: bool,
}

/// Add crate owners.
Expand All @@ -102,14 +120,14 @@ pub async fn get_user_owners(state: AppState, path: CratePath) -> AppResult<Eras
("cookie" = []),
),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(ModifyResponse))),
)]
pub async fn add_owners(
app: AppState,
path: CratePath,
parts: Parts,
Json(body): Json<ChangeOwnersRequest>,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<ModifyResponse>> {
modify_owners(app, path.name, parts, body, true).await
}

Expand All @@ -123,14 +141,14 @@ pub async fn add_owners(
("cookie" = []),
),
tag = "owners",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(ModifyResponse))),
)]
pub async fn remove_owners(
app: AppState,
path: CratePath,
parts: Parts,
Json(body): Json<ChangeOwnersRequest>,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<ModifyResponse>> {
modify_owners(app, path.name, parts, body, false).await
}

Expand All @@ -146,7 +164,7 @@ async fn modify_owners(
parts: Parts,
body: ChangeOwnersRequest,
add: bool,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<ModifyResponse>> {
let logins = body.owners;

// Bound the number of invites processed per request to limit the cost of
Expand All @@ -166,7 +184,7 @@ async fn modify_owners(

let user = auth.user();

let (comma_sep_msg, emails) = conn
let (msg, emails) = conn
.transaction(|conn| {
let app = app.clone();
async move {
Expand Down Expand Up @@ -281,7 +299,7 @@ async fn modify_owners(
}
}

Ok(json!({ "msg": comma_sep_msg, "ok": true }))
Ok(Json(ModifyResponse { msg, ok: true }))
}

/// Invite `login` as an owner of this crate, returning the created
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/krate/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
("cookie" = []),
),
tag = "publish",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(GoodCrate))),
)]
pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<GoodCrate>> {
let stream = body.into_data_stream();
Expand Down
33 changes: 25 additions & 8 deletions src/controllers/krate/rev_deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,43 @@ use crate::controllers::krate::CratePath;
use crate::models::{CrateName, User, Version, VersionOwnerAction};
use crate::util::errors::AppResult;
use crate::views::{EncodableDependency, EncodableVersion};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use axum::Json;
use crates_io_database::schema::{crates, users, versions};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use http::request::Parts;

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct RevDepsResponse {
/// The list of reverse dependencies of the crate.
dependencies: Vec<EncodableDependency>,

/// The versions referenced in the `dependencies` field.
versions: Vec<EncodableVersion>,

#[schema(inline)]
meta: RevDepsMeta,
}

#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct RevDepsMeta {
#[schema(example = 32)]
total: i64,
}

/// List reverse dependencies of a crate.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/reverse_dependencies",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
responses((status = 200, description = "Successful Response", body = inline(RevDepsResponse))),
)]
pub async fn list_reverse_dependencies(
app: AppState,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
) -> AppResult<Json<RevDepsResponse>> {
let mut conn = app.db_read().await?;

let pagination_options = PaginationOptions::builder().gather(&req)?;
Expand Down Expand Up @@ -64,9 +81,9 @@ pub async fn list_reverse_dependencies(
})
.collect::<Vec<_>>();

Ok(json!({
"dependencies": rev_deps,
"versions": versions,
"meta": { "total": total },
Ok(Json(RevDepsResponse {
dependencies: rev_deps,
versions,
meta: RevDepsMeta { total },
}))
}
Loading