warehouse/src/owners.rs

134 lines
3.5 KiB
Rust

use std::sync::Arc;
use async_trait::async_trait;
use axum::{
body::HttpBody,
extract::{FromRequest, Path, RequestParts},
BoxError, Extension, Json as AxumJson,
};
use futures_util::StreamExt;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sqlx::{query, query_as};
use crate::{auth, db::PgU32, db_error, Auth, Errors, RespResult, State, Success};
pub struct Json<T>(T);
#[async_trait]
impl<T, B> FromRequest<B> for Json<T>
where
T: DeserializeOwned,
B: HttpBody + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Rejection = Errors;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let res = AxumJson::<T>::from_request(req).await;
match res {
Ok(AxumJson(v)) => Ok(Json(v)),
Err(e) => Err(Errors::new(format_args!("Invalid json data: {e}"))),
}
}
}
#[derive(Serialize)]
pub struct ListOwnersResponse {
users: Vec<User>,
}
#[derive(Serialize)]
struct User {
id: u32,
login: String,
name: Option<String>,
}
pub async fn list_owners(
Path(crate_name): Path<String>,
Auth(_auth_user): Auth,
Extension(state): Extension<Arc<State>>,
) -> RespResult<AxumJson<ListOwnersResponse>> {
let mut users = query_as("SELECT users.id, users.login, users.name FROM crates INNER JOIN users ON users.id = ANY (crates.owners) WHERE crates.name = $1")
.bind(&crate_name)
.fetch(&state.db);
let mut users2 = Vec::new();
while let Some(user) = users.next().await.transpose().map_err(db_error)? {
let _: (PgU32, _, _) = user;
users2.push(User {
id: user.0 .0,
login: user.1,
name: user.2,
});
}
Ok(AxumJson(ListOwnersResponse { users: users2 }))
}
#[derive(Deserialize)]
pub struct AddRemoveOwnersRequest {
users: Vec<String>,
}
#[derive(Serialize)]
pub struct AddRemoveOwnersResponse {
#[serde(flatten)]
success: Success,
msg: String,
}
pub async fn add_owners(
Json(request): Json<AddRemoveOwnersRequest>,
Path(crate_name): Path<String>,
Auth(auth_user): Auth,
Extension(state): Extension<Arc<State>>,
) -> RespResult<AxumJson<AddRemoveOwnersResponse>> {
auth(&crate_name, &auth_user, &state).await?;
query(
r"UPDATE crates SET owners = owners || (
SELECT array_agg(users.id) FROM users INNER JOIN unnest($1) ON users.login = unnest
) WHERE name = $2",
)
.bind(&request.users)
.bind(&crate_name)
.execute(&state.db)
.await
.map_err(db_error)?;
Ok(AxumJson(AddRemoveOwnersResponse {
success: Success,
msg: format!("adding {:?} to crate {}", request.users, crate_name),
}))
}
pub async fn remove_owners(
Json(request): Json<AddRemoveOwnersRequest>,
Path(crate_name): Path<String>,
Auth(auth_user): Auth,
Extension(state): Extension<Arc<State>>,
) -> RespResult<AxumJson<AddRemoveOwnersResponse>> {
auth(&crate_name, &auth_user, &state).await?;
query(
r"UPDATE crates SET owners = (
SELECT array_agg(unnest) FROM (
SELECT unnest(owners) EXCEPT (SELECT users.id FROM users INNER JOIN unnest($1) ON users.login = unnest)
) t (unnest)
) WHERE name = $2",
)
.bind(&request.users)
.bind(&crate_name)
.execute(&state.db)
.await
.map_err(db_error)?;
Ok(AxumJson(AddRemoveOwnersResponse {
success: Success,
msg: format!("removed {:?} from crate {}", request.users, crate_name),
}))
}