136 lines
3.5 KiB
Rust
136 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};
|
|
|
|
// TODO: custom `Json` errors
|
|
|
|
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),
|
|
}))
|
|
}
|