Keep request handlers top of each file
Diff
chartered-web/src/main.rs | 5 +++++
chartered-web/src/middleware/auth.rs | 13 ++++++++++++-
chartered-web/src/middleware/logging.rs | 2 ++
chartered-web/src/endpoints/cargo_api/download.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
chartered-web/src/endpoints/cargo_api/mod.rs | 9 +++++++++
chartered-web/src/endpoints/cargo_api/owners.rs | 64 +++++++++++++++++++++++++++++++++++++---------------------------
chartered-web/src/endpoints/cargo_api/publish.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
chartered-web/src/endpoints/cargo_api/yank.rs | 47 +++++++++++++++++++++++++++--------------------
chartered-web/src/endpoints/web_api/ssh_key.rs | 41 ++++++++++++++++++++++-------------------
chartered-web/src/endpoints/web_api/auth/logout.rs | 3 +++
chartered-web/src/endpoints/web_api/auth/mod.rs | 2 ++
chartered-web/src/endpoints/web_api/auth/openid.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
chartered-web/src/endpoints/web_api/auth/password.rs | 44 ++++++++++++++++++++++++--------------------
chartered-web/src/endpoints/web_api/crates/info.rs | 50 ++++++++++++++++++++++++++++++--------------------
chartered-web/src/endpoints/web_api/crates/members.rs | 60 +++++++++++++++++++++++++++++++++++++-----------------------
chartered-web/src/endpoints/web_api/crates/most_downloaded.rs | 34 ++++++++++++++++++----------------
chartered-web/src/endpoints/web_api/crates/recently_created.rs | 35 +++++++++++++++++++----------------
chartered-web/src/endpoints/web_api/crates/recently_updated.rs | 34 ++++++++++++++++++----------------
chartered-web/src/endpoints/web_api/crates/search.rs | 46 ++++++++++++++++++++++++++--------------------
chartered-web/src/endpoints/web_api/organisations/crud.rs | 37 ++++++++++++++++++++-----------------
chartered-web/src/endpoints/web_api/organisations/info.rs | 40 ++++++++++++++++++++++++----------------
chartered-web/src/endpoints/web_api/organisations/list.rs | 35 +++++++++++++++++++----------------
chartered-web/src/endpoints/web_api/organisations/members.rs | 28 +++++++++++++++++-----------
chartered-web/src/endpoints/web_api/users/info.rs | 23 ++++++++++++++---------
chartered-web/src/endpoints/web_api/users/search.rs | 40 +++++++++++++++++++++++-----------------
25 files changed, 585 insertions(+), 419 deletions(-)
@@ -58,8 +58,11 @@
#[tokio::main]
#[allow(clippy::semicolon_if_nothing_returned)]
async fn main() -> Result<(), InitError> {
let opts: Opts = Opts::parse();
std::env::set_var(
"RUST_LOG",
match opts.verbose {
@@ -71,11 +74,13 @@
let config: config::Config = toml::from_slice(&std::fs::read(&opts.config)?)?;
tracing_subscriber::fmt::init();
let bind_address = config.bind_address;
let pool = chartered_db::init(&config.database_uri)?;
let middleware_stack = ServiceBuilder::new()
.layer_fn(middleware::logging::LoggingMiddleware)
.into_inner();
@@ -1,3 +1,6 @@
use axum::{
body::{box_body, Body, BoxBody},
extract::{self, FromRequest, RequestParts},
@@ -41,12 +44,15 @@
Box::pin(async move {
let mut req = RequestParts::new(req);
let params = extract::Path::<HashMap<String, String>>::from_request(&mut req)
.await
.unwrap();
let key = params.get("key").map(String::as_str).unwrap_or_default();
let db = req
.extensions()
.unwrap()
@@ -54,6 +60,8 @@
.unwrap()
.clone();
let (session, user) = match User::find_by_session_key(db, String::from(key))
.await
.unwrap()
@@ -72,9 +80,12 @@
}
};
req.extensions_mut().unwrap().insert(user);
req.extensions_mut().unwrap().insert(session);
let response: Response<BoxBody> = inner.call(req.try_into_request().unwrap()).await?;
Ok(response)
@@ -1,3 +1,5 @@
use axum::{
extract::{self, FromRequest, RequestParts},
http::{Request, Response},
@@ -1,3 +1,7 @@
use axum::{
body::{Full, HttpBody},
extract,
@@ -6,50 +10,9 @@
};
use bytes::Bytes;
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use chartered_fs::{FilePointer, FileSystem};
use chartered_fs::{FilePointer, FileReference, FileSystem};
use std::{str::FromStr, sync::Arc};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
#[error("Failed to fetch crate file: {0}")]
File(#[from] Box<chartered_fs::Error>),
#[error("The requested version does not exist for the crate")]
NoVersion,
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(e) => e.status_code(),
Self::File(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::NoVersion => StatusCode::NOT_FOUND,
}
}
}
define_error_response!(Error);
pub enum ResponseOrRedirect {
Response(Vec<u8>),
Redirect(Redirect),
}
impl IntoResponse for ResponseOrRedirect {
type Body = Full<Bytes>;
type BodyError = <Self::Body as HttpBody>::Error;
fn into_response(self) -> Response<Self::Body> {
match self {
Self::Response(v) => v.into_response(),
Self::Redirect(v) => v.into_response().map(|_| Full::from(Bytes::new())),
}
}
}
pub async fn handle(
extract::Path((_session_key, organisation, name, version)): extract::Path<(
@@ -73,17 +36,62 @@
.increment_download_count(db.clone()),
);
let version = crate_with_permissions
.version(db, version)
.await?
.ok_or(Error::NoVersion)?;
let file_ref = chartered_fs::FileReference::from_str(&version.filesystem_object).unwrap();
let file_ref = FileReference::from_str(&version.filesystem_object).map_err(Box::new)?;
let res = fs.read(file_ref).await.map_err(Box::new)?;
match res {
FilePointer::Redirect(uri) => Ok(ResponseOrRedirect::Redirect(Redirect::to(uri))),
FilePointer::Content(content) => Ok(ResponseOrRedirect::Response(content)),
}
}
pub enum ResponseOrRedirect {
Response(Vec<u8>),
Redirect(Redirect),
}
impl IntoResponse for ResponseOrRedirect {
type Body = Full<Bytes>;
type BodyError = <Self::Body as HttpBody>::Error;
fn into_response(self) -> Response<Self::Body> {
match self {
Self::Response(v) => v.into_response(),
Self::Redirect(v) => v.into_response().map(|_| Full::from(Bytes::new())),
}
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
#[error("Failed to fetch crate file: {0}")]
File(#[from] Box<chartered_fs::Error>),
#[error("The requested version does not exist for the crate")]
NoVersion,
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(e) => e.status_code(),
Self::File(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::NoVersion => StatusCode::NOT_FOUND,
}
}
}
define_error_response!(Error);
@@ -1,3 +1,11 @@
mod download;
mod owners;
mod publish;
@@ -12,6 +20,7 @@
use futures::future::Future;
use std::convert::Infallible;
pub fn routes() -> Router<
impl tower::Service<
Request<Body>,
@@ -1,37 +1,13 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
#[derive(Serialize)]
pub struct GetResponse {
users: Vec<GetResponseUser>,
}
#[derive(Serialize)]
pub struct GetResponseUser {
login: String,
name: Option<String>,
}
pub async fn handle_get(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
@@ -41,6 +17,7 @@
let crate_with_permissions =
Arc::new(Crate::find_by_name(db.clone(), user.id, organisation, name).await?);
let users = crate_with_permissions
.owners(db)
.await?
@@ -53,4 +30,33 @@
.collect();
Ok(Json(GetResponse { users }))
}
#[derive(Serialize)]
pub struct GetResponse {
users: Vec<GetResponseUser>,
}
#[derive(Serialize)]
pub struct GetResponseUser {
login: String,
name: Option<String>,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,3 +1,8 @@
use axum::extract;
use bytes::Bytes;
use chartered_db::{crates::Crate, users::User, ConnectionPool};
@@ -8,64 +13,6 @@
use sha2::{Digest, Sha256};
use std::{borrow::Cow, convert::TryInto, sync::Arc};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
#[error("Invalid JSON from client: {0}")]
JsonParse(#[from] serde_json::Error),
#[error("Invalid body")]
MetadataParse,
#[error("expected a valid crate name to start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters ")]
InvalidCrateName,
#[error("Failed to push crate file to storage: {0}")]
File(#[from] Box<chartered_fs::Error>),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(e) => e.status_code(),
Self::JsonParse(_) | Self::MetadataParse | Self::InvalidCrateName => {
StatusCode::BAD_REQUEST
}
Self::File(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
define_error_response!(Error);
#[derive(Serialize, Debug, Default)]
pub struct PublishCrateResponse {
warnings: PublishCrateResponseWarnings,
}
#[derive(Serialize, Debug, Default)]
pub struct PublishCrateResponseWarnings {
invalid_categories: Vec<String>,
invalid_badges: Vec<String>,
other: Vec<String>,
}
fn validate_crate_name(name: &str) -> bool {
const MAX_NAME_LENGTH: usize = 64;
let starts_with_alphabetic = name
.chars()
.next()
.map(|c| c.is_ascii_alphabetic())
.unwrap_or_default();
let is_alphanumeric = name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_');
let is_under_max_length = name.len() < MAX_NAME_LENGTH;
starts_with_alphabetic && is_alphanumeric && is_under_max_length
}
pub async fn handle(
extract::Path((_session_key, organisation)): extract::Path<(String, String)>,
@@ -74,13 +21,18 @@
extract::Extension(fs): extract::Extension<Arc<FileSystem>>,
body: Bytes,
) -> Result<axum::response::Json<PublishCrateResponse>, Error> {
let (_, (metadata_bytes, crate_bytes)) = parse(body).map_err(|_| Error::MetadataParse)?;
let metadata: Metadata<'_> = serde_json::from_slice(&metadata_bytes)?;
if !validate_crate_name(&metadata.inner.name) {
return Err(Error::InvalidCrateName);
}
let crate_with_permissions = Crate::find_by_name(
db.clone(),
user.id,
@@ -89,6 +41,8 @@
)
.await;
let crate_with_permissions = match crate_with_permissions {
Ok(v) => Arc::new(v),
Err(chartered_db::Error::MissingCrate) => {
@@ -104,10 +58,14 @@
Err(e) => return Err(e.into()),
};
let checksum = hex::encode(Sha256::digest(&crate_bytes));
let file_ref = fs.write(crate_bytes).await.map_err(Box::new)?;
crate_with_permissions
.publish_version(
db,
@@ -123,6 +81,9 @@
Ok(axum::response::Json(PublishCrateResponse::default()))
}
fn parse(body: impl Into<BytesWrapper>) -> nom::IResult<BytesWrapper, (Bytes, Bytes)> {
use nom::{bytes::complete::take, combinator::map_res};
use std::array::TryFromSliceError;
@@ -139,8 +100,29 @@
let (rest, crate_bytes) = take(crate_length)(rest)?;
Ok((rest, (metadata_bytes.into(), crate_bytes.into())))
}
fn validate_crate_name(name: &str) -> bool {
const MAX_NAME_LENGTH: usize = 64;
let starts_with_alphabetic = name
.chars()
.next()
.map(|c| c.is_ascii_alphabetic())
.unwrap_or_default();
let is_alphanumeric = name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_');
let is_under_max_length = name.len() < MAX_NAME_LENGTH;
starts_with_alphabetic && is_alphanumeric && is_under_max_length
}
#[allow(dead_code)]
#[derive(Deserialize, Debug)]
pub struct Metadata<'a> {
@@ -162,6 +144,7 @@
inner: MetadataCrateVersion<'a>,
}
#[derive(Deserialize, Debug)]
pub struct MetadataCrateVersion<'a> {
#[serde(borrow)]
@@ -227,6 +210,48 @@
kind: Cow::Owned(us.kind.into_owned()),
registry: us.registry.map(|v| Cow::Owned(v.into_owned())),
package: package.map(Cow::Owned),
}
}
}
#[derive(Serialize, Debug, Default)]
pub struct PublishCrateResponse {
warnings: PublishCrateResponseWarnings,
}
#[derive(Serialize, Debug, Default)]
pub struct PublishCrateResponseWarnings {
invalid_categories: Vec<String>,
invalid_badges: Vec<String>,
other: Vec<String>,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
#[error("Invalid JSON from client: {0}")]
JsonParse(#[from] serde_json::Error),
#[error("Invalid body")]
MetadataParse,
#[error("expected a valid crate name to start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters ")]
InvalidCrateName,
#[error("Failed to push crate file to storage: {0}")]
File(#[from] Box<chartered_fs::Error>),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(e) => e.status_code(),
Self::JsonParse(_) | Self::MetadataParse | Self::InvalidCrateName => {
StatusCode::BAD_REQUEST
}
Self::File(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
define_error_response!(Error);
@@ -1,29 +1,13 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
#[derive(Serialize)]
pub struct Response {
ok: bool,
}
pub async fn handle_yank(
extract::Path((_session_key, organisation, name, version)): extract::Path<(
@@ -63,4 +47,25 @@
.await?;
Ok(Json(Response { ok: true }))
}
#[derive(Serialize)]
pub struct Response {
ok: bool,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,3 +1,6 @@
use chartered_db::{users::User, ConnectionPool};
use axum::{extract, Json};
@@ -9,20 +12,6 @@
use tracing::warn;
use crate::endpoints::ErrorResponse;
#[derive(Serialize)]
pub struct GetResponse {
keys: Vec<GetResponseKey>,
}
#[derive(Serialize)]
pub struct GetResponseKey {
uuid: Uuid,
name: String,
fingerprint: String,
created_at: DateTime<Utc>,
last_used_at: Option<DateTime<Utc>>,
}
pub async fn handle_get(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -47,11 +36,6 @@
.collect();
Ok(Json(GetResponse { keys }))
}
#[derive(Deserialize)]
pub struct PutRequest {
key: String,
}
pub async fn handle_put(
@@ -78,6 +62,25 @@
} else {
Err(Error::NonExistentKey)
}
}
#[derive(Serialize)]
pub struct GetResponse {
keys: Vec<GetResponseKey>,
}
#[derive(Serialize)]
pub struct GetResponseKey {
uuid: Uuid,
name: String,
fingerprint: String,
created_at: DateTime<Utc>,
last_used_at: Option<DateTime<Utc>>,
}
#[derive(Deserialize)]
pub struct PutRequest {
key: String,
}
#[derive(Error, Debug)]
@@ -1,3 +1,6 @@
use axum::{extract, Json};
use chartered_db::users::UserSession;
use chartered_db::ConnectionPool;
@@ -54,6 +54,8 @@
picture_url: Option<String>,
}
pub async fn login(
db: ConnectionPool,
user: User,
@@ -1,3 +1,7 @@
use crate::config::{Config, OidcClients};
use axum::{extract, Json};
use chacha20poly1305::{
@@ -11,12 +15,8 @@
use thiserror::Error;
pub type Nonce = [u8; 16];
#[derive(Serialize)]
pub struct ListProvidersResponse {
providers: Vec<String>,
}
#[allow(clippy::unused_async)]
pub async fn list_providers(
extract::Extension(oidc_clients): extract::Extension<Arc<OidcClients>>,
@@ -28,19 +28,11 @@
.map(std::string::ToString::to_string)
.collect(),
})
}
#[derive(Serialize, Deserialize, Debug)]
pub struct State {
provider: String,
nonce: Nonce,
}
#[derive(Serialize)]
pub struct BeginResponse {
redirect_url: String,
}
#[allow(clippy::unused_async)]
pub async fn begin_oidc(
extract::Path(provider): extract::Path<String>,
@@ -64,17 +56,10 @@
Ok(Json(BeginResponse {
redirect_url: auth_url.to_string(),
}))
}
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct CompleteOidcParams {
state: String,
code: String,
scope: Option<String>,
prompt: Option<String>,
}
pub async fn complete_oidc(
extract::Query(params): extract::Query<CompleteOidcParams>,
extract::Extension(config): extract::Extension<Arc<Config>>,
@@ -83,8 +68,11 @@
user_agent: Option<extract::TypedHeader<headers::UserAgent>>,
addr: extract::ConnectInfo<std::net::SocketAddr>,
) -> Result<Json<super::LoginResponse>, Error> {
let state: State = serde_json::from_slice(&decrypt_url_safe(¶ms.state, &config)?)?;
let client = oidc_clients
.get(&state.provider)
.ok_or(Error::UnknownOauthProvider)?;
@@ -92,18 +80,26 @@
let mut token: Token = client.request_token(¶ms.code).await?.into();
if let Some(id_token) = token.id_token.as_mut() {
client.decode_token(id_token)?;
let nonce = base64::encode_config(state.nonce, base64::URL_SAFE_NO_PAD);
client.validate_token(id_token, Some(nonce.as_str()), None)?;
} else {
return Err(Error::MissingToken);
}
let userinfo = client.request_userinfo(&token).await?;
let user = User::find_or_create(
db.clone(),
format!("{}:{}", state.provider, userinfo.sub.unwrap()),
userinfo.name,
userinfo.nickname,
@@ -113,11 +109,14 @@
)
.await?;
Ok(Json(super::login(db, user, user_agent, addr).await?))
}
const NONCE_LEN: usize = 12;
fn encrypt_url_safe(input: &[u8], config: &Config) -> Result<String, Error> {
let cipher = ChaCha20Poly1305::new(&config.encryption_key);
@@ -130,6 +129,7 @@
Ok(base64::encode_config(&ciphertext, base64::URL_SAFE_NO_PAD))
}
fn decrypt_url_safe(input: &str, config: &Config) -> Result<Vec<u8>, Error> {
let cipher = ChaCha20Poly1305::new(&config.encryption_key);
@@ -140,6 +140,31 @@
cipher
.decrypt(ciphertext_nonce, ciphertext.as_ref())
.map_err(Error::from)
}
#[derive(Serialize)]
pub struct ListProvidersResponse {
providers: Vec<String>,
}
#[derive(Serialize)]
pub struct BeginResponse {
redirect_url: String,
}
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct CompleteOidcParams {
state: String,
code: String,
scope: Option<String>,
prompt: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct State {
provider: String,
nonce: Nonce,
}
#[derive(Error, Debug)]
@@ -1,28 +1,9 @@
use axum::{extract, Json};
use chartered_db::{users::User, ConnectionPool};
use serde::Deserialize;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to query database")]
Database(#[from] chartered_db::Error),
#[error("Invalid username/password")]
UnknownUser,
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::UnknownUser => StatusCode::FORBIDDEN,
}
}
}
define_error_response!(Error);
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -48,4 +29,25 @@
pub struct Request {
username: String,
password: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to query database")]
Database(#[from] chartered_db::Error),
#[error("Invalid username/password")]
UnknownUser,
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
use axum::http::StatusCode;
match self {
Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::UnknownUser => StatusCode::FORBIDDEN,
}
}
}
define_error_response!(Error);
@@ -1,3 +1,10 @@
use axum::{body::Full, extract, response::IntoResponse, Json};
use bytes::Bytes;
use chartered_db::{crates::Crate, users::User, ConnectionPool};
@@ -6,23 +13,7 @@
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -31,16 +22,12 @@
let crate_with_permissions =
Arc::new(Crate::find_by_name(db.clone(), user.id, organisation, name).await?);
let versions = crate_with_permissions
.clone()
.versions_with_uploader(db)
.await?;
Ok(Json(Response {
info: (&crate_with_permissions.crate_).into(),
versions: versions
@@ -57,6 +44,11 @@
})
.collect(),
})
.into_response())
}
@@ -102,6 +94,22 @@
repository: crate_.repository.as_deref(),
homepage: crate_.homepage.as_deref(),
documentation: crate_.documentation.as_deref(),
}
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,3 +1,7 @@
use axum::{extract, Json};
use chartered_db::{
crates::Crate, permissions::UserPermission, users::User, uuid::Uuid, ConnectionPool,
@@ -7,21 +11,10 @@
use thiserror::Error;
use crate::endpoints::ErrorResponse;
#[derive(Serialize)]
pub struct GetResponse {
allowed_permissions: &'static [&'static str],
members: Vec<GetResponseMember>,
}
#[derive(Deserialize, Serialize)]
pub struct GetResponseMember {
uuid: Uuid,
display_name: String,
picture_url: Option<String>,
permissions: UserPermission,
}
pub async fn handle_get(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -46,14 +39,9 @@
allowed_permissions: UserPermission::names(),
members,
}))
}
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
user_uuid: chartered_db::uuid::Uuid,
permissions: UserPermission,
}
pub async fn handle_patch(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -77,6 +65,7 @@
Ok(Json(ErrorResponse { error: None }))
}
pub async fn handle_put(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -95,13 +84,9 @@
.await?;
Ok(Json(ErrorResponse { error: None }))
}
#[derive(Deserialize)]
pub struct DeleteRequest {
user_uuid: chartered_db::uuid::Uuid,
}
pub async fn handle_delete(
extract::Path((_session_key, organisation, name)): extract::Path<(String, String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -120,6 +105,31 @@
.await?;
Ok(Json(ErrorResponse { error: None }))
}
#[derive(Serialize)]
pub struct GetResponse {
allowed_permissions: &'static [&'static str],
members: Vec<GetResponseMember>,
}
#[derive(Deserialize, Serialize)]
pub struct GetResponseMember {
uuid: Uuid,
display_name: String,
picture_url: Option<String>,
permissions: UserPermission,
}
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
user_uuid: chartered_db::uuid::Uuid,
permissions: UserPermission,
}
#[derive(Deserialize)]
pub struct DeleteRequest {
user_uuid: chartered_db::uuid::Uuid,
}
#[derive(Error, Debug)]
@@ -1,24 +1,10 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -48,4 +34,20 @@
name: String,
downloads: i32,
organisation: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,25 +1,12 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use chrono::{DateTime, TimeZone, Utc};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -49,4 +36,20 @@
name: String,
created_at: DateTime<Utc>,
organisation: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,24 +1,10 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, users::User, ConnectionPool};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -48,4 +34,20 @@
name: String,
version: String,
organisation: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,29 +1,12 @@
use axum::{extract, Json};
use chartered_db::{crates::Crate, permissions::UserPermission, users::User, ConnectionPool};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;
#[derive(Deserialize)]
pub struct RequestParams {
q: String,
}
#[derive(Serialize)]
pub struct Response {
crates: Vec<ResponseCrate>,
}
#[derive(Serialize)]
pub struct ResponseCrate {
organisation: String,
name: String,
description: Option<String>,
version: String,
homepage: Option<String>,
repository: Option<String>,
permissions: UserPermission,
}
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -63,6 +46,27 @@
.await?;
Ok(Json(Response { crates }))
}
#[derive(Deserialize)]
pub struct RequestParams {
q: String,
}
#[derive(Serialize)]
pub struct Response {
crates: Vec<ResponseCrate>,
}
#[derive(Serialize)]
pub struct ResponseCrate {
organisation: String,
name: String,
description: Option<String>,
version: String,
homepage: Option<String>,
repository: Option<String>,
permissions: UserPermission,
}
#[derive(Error, Debug)]
@@ -1,3 +1,6 @@
use axum::{extract, Json};
use chartered_db::{organisations::Organisation, users::User, ConnectionPool};
use serde::Deserialize;
@@ -5,6 +8,23 @@
use thiserror::Error;
use crate::endpoints::ErrorResponse;
pub async fn handle_put(
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Extension(user): extract::Extension<Arc<User>>,
extract::Json(req): extract::Json<PutRequest>,
) -> Result<Json<ErrorResponse>, Error> {
Organisation::create(db, req.name, req.description, req.public, user.id).await?;
Ok(Json(ErrorResponse { error: None }))
}
#[derive(Deserialize)]
pub struct PutRequest {
name: String,
description: String,
public: bool,
}
#[derive(Error, Debug)]
pub enum Error {
@@ -21,20 +41,3 @@
}
define_error_response!(Error);
#[derive(Deserialize)]
pub struct PutRequest {
name: String,
description: String,
public: bool,
}
pub async fn handle_put(
extract::Extension(db): extract::Extension<ConnectionPool>,
extract::Extension(user): extract::Extension<Arc<User>>,
extract::Json(req): extract::Json<PutRequest>,
) -> Result<Json<ErrorResponse>, Error> {
Organisation::create(db, req.name, req.description, req.public, user.id).await?;
Ok(Json(ErrorResponse { error: None }))
}
@@ -1,3 +1,7 @@
use axum::{extract, Json};
use chartered_db::{
organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool,
@@ -5,22 +9,6 @@
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle_get(
extract::Path((_session_key, organisation)): extract::Path<(String, String)>,
@@ -30,10 +18,13 @@
let organisation =
Arc::new(Organisation::find_by_name(db.clone(), user.id, organisation).await?);
let can_manage_users = organisation
.permissions()
.contains(UserPermission::MANAGE_USERS);
let (crates, users) = tokio::try_join!(
organisation.clone().crates(db.clone()),
organisation.clone().members(db),
@@ -41,6 +32,7 @@
Ok(Json(Response {
description: organisation.organisation().description.to_string(),
possible_permissions: can_manage_users.then(UserPermission::all),
crates: crates
.into_iter()
@@ -81,4 +73,20 @@
display_name: String,
picture_url: Option<String>,
permissions: Option<UserPermission>,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,24 +1,11 @@
use axum::{extract, Json};
use chartered_db::{organisations::Organisation, users::User, ConnectionPool};
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
pub async fn handle_get(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -46,4 +33,20 @@
pub struct ResponseOrganisation {
name: String,
description: String,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Database(#[from] chartered_db::Error),
}
impl Error {
pub fn status_code(&self) -> axum::http::StatusCode {
match self {
Self::Database(e) => e.status_code(),
}
}
}
define_error_response!(Error);
@@ -1,3 +1,6 @@
use axum::{extract, Json};
use chartered_db::{
organisations::Organisation, permissions::UserPermission, users::User, ConnectionPool,
@@ -7,13 +10,8 @@
use thiserror::Error;
use crate::endpoints::ErrorResponse;
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
user_uuid: chartered_db::uuid::Uuid,
permissions: UserPermission,
}
pub async fn handle_patch(
extract::Path((_session_key, organisation)): extract::Path<(String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -37,6 +35,7 @@
Ok(Json(ErrorResponse { error: None }))
}
pub async fn handle_put(
extract::Path((_session_key, organisation)): extract::Path<(String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -55,13 +54,9 @@
.await?;
Ok(Json(ErrorResponse { error: None }))
}
#[derive(Deserialize)]
pub struct DeleteRequest {
user_uuid: chartered_db::uuid::Uuid,
}
pub async fn handle_delete(
extract::Path((_session_key, organisation)): extract::Path<(String, String)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -78,6 +73,17 @@
organisation.delete_member(db, action_user.id).await?;
Ok(Json(ErrorResponse { error: None }))
}
#[derive(Deserialize)]
pub struct PutOrPatchRequest {
user_uuid: chartered_db::uuid::Uuid,
permissions: UserPermission,
}
#[derive(Deserialize)]
pub struct DeleteRequest {
user_uuid: chartered_db::uuid::Uuid,
}
#[derive(Error, Debug)]
@@ -1,7 +1,21 @@
use axum::{extract, Json};
use chartered_db::{users::User, ConnectionPool};
use serde::Serialize;
use thiserror::Error;
pub async fn handle(
extract::Path((_session_key, uuid)): extract::Path<(String, chartered_db::uuid::Uuid)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
) -> Result<Json<Response>, Error> {
let user = User::find_by_uuid(db, uuid).await?.ok_or(Error::NotFound)?;
Ok(Json(user.into()))
}
#[derive(Serialize)]
pub struct Response {
@@ -26,15 +40,6 @@
picture_url: user.picture_url,
}
}
}
pub async fn handle(
extract::Path((_session_key, uuid)): extract::Path<(String, chartered_db::uuid::Uuid)>,
extract::Extension(db): extract::Extension<ConnectionPool>,
) -> Result<Json<Response>, Error> {
let user = User::find_by_uuid(db, uuid).await?.ok_or(Error::NotFound)?;
Ok(Json(user.into()))
}
#[derive(Error, Debug)]
@@ -1,24 +1,13 @@
use axum::{extract, Json};
use chartered_db::{users::User, ConnectionPool};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Deserialize)]
pub struct RequestParams {
q: String,
}
#[derive(Serialize)]
pub struct Response {
users: Vec<ResponseUser>,
}
#[derive(Serialize)]
pub struct ResponseUser {
user_uuid: chartered_db::uuid::Uuid,
display_name: String,
picture_url: Option<String>,
}
pub async fn handle(
extract::Extension(db): extract::Extension<ConnectionPool>,
@@ -35,6 +24,23 @@
.collect();
Ok(Json(Response { users }))
}
#[derive(Deserialize)]
pub struct RequestParams {
q: String,
}
#[derive(Serialize)]
pub struct Response {
users: Vec<ResponseUser>,
}
#[derive(Serialize)]
pub struct ResponseUser {
user_uuid: chartered_db::uuid::Uuid,
display_name: String,
picture_url: Option<String>,
}
#[derive(Error, Debug)]