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); #[async_trait] impl FromRequest for Json where T: DeserializeOwned, B: HttpBody + Send, B::Data: Send, B::Error: Into, { type Rejection = Errors; async fn from_request(req: &mut RequestParts) -> Result { let res = AxumJson::::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, } #[derive(Serialize)] struct User { id: u32, login: String, name: Option, } pub async fn list_owners( Path(crate_name): Path, Auth(_auth_user): Auth, Extension(state): Extension>, ) -> RespResult> { 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, } #[derive(Serialize)] pub struct AddRemoveOwnersResponse { #[serde(flatten)] success: Success, msg: String, } pub async fn add_owners( Json(request): Json, Path(crate_name): Path, Auth(auth_user): Auth, Extension(state): Extension>, ) -> RespResult> { 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, Path(crate_name): Path, Auth(auth_user): Auth, Extension(state): Extension>, ) -> RespResult> { 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), })) }