From 77e0a176bdffb2b2995334bca583f9caaa5846ad Mon Sep 17 00:00:00 2001 From: xphoniex Date: Tue, 22 Nov 2022 10:55:13 +0000 Subject: [PATCH] Migrate `/v1/delegates` route Signed-off-by: xphoniex --- radicle-httpd/src/api.rs | 2 +- radicle-httpd/src/api/error.rs | 4 ++ radicle-httpd/src/api/v1.rs | 4 +- radicle-httpd/src/api/v1/delegates.rs | 78 +++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 radicle-httpd/src/api/v1/delegates.rs diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index 264491e1..215650f3 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -99,7 +99,7 @@ async fn root_handler(Extension(ctx): Extension) -> impl IntoResponse { "type": "GET" }, { - "href": "/v1/delegates/:urn/projects", + "href": "/v1/delegates/:did/projects", "rel": "projects", "type": "GET" } diff --git a/radicle-httpd/src/api/error.rs b/radicle-httpd/src/api/error.rs index 78ff62df..b8c5e1d1 100644 --- a/radicle-httpd/src/api/error.rs +++ b/radicle-httpd/src/api/error.rs @@ -39,6 +39,10 @@ pub enum Error { /// An error occurred while parsing the siwe message. #[error(transparent)] SiweParse(#[from] siwe::ParseError), + + /// Storage error. + #[error(transparent)] + StorageError(#[from] radicle::storage::Error), } impl Error { diff --git a/radicle-httpd/src/api/v1.rs b/radicle-httpd/src/api/v1.rs index f09751f1..b39bc609 100644 --- a/radicle-httpd/src/api/v1.rs +++ b/radicle-httpd/src/api/v1.rs @@ -1,3 +1,4 @@ +mod delegates; mod node; mod sessions; @@ -8,7 +9,8 @@ use crate::api::Context; pub fn router(ctx: Context) -> Router { let routes = Router::new() .merge(node::router(ctx.clone())) - .merge(sessions::router(ctx)); + .merge(sessions::router(ctx.clone())) + .merge(delegates::router(ctx)); Router::new().nest("/v1", routes) } diff --git a/radicle-httpd/src/api/v1/delegates.rs b/radicle-httpd/src/api/v1/delegates.rs new file mode 100644 index 00000000..05abb9d5 --- /dev/null +++ b/radicle-httpd/src/api/v1/delegates.rs @@ -0,0 +1,78 @@ +use axum::response::IntoResponse; +use axum::routing::get; +use axum::{Extension, Json, Router}; +use serde::Serialize; + +use radicle::cob::store::Store; +use radicle::git::Oid; +use radicle::identity::project::Payload; +use radicle::identity::{Did, Doc}; +use radicle::storage::{ReadRepository, WriteStorage}; + +use crate::api::axum_extra::{Path, Query}; +use crate::api::error::Error; +use crate::api::Context; +use crate::api::PaginationQuery; + +/// Project info. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Info { + /// Project metadata. + #[serde(flatten)] + pub payload: Payload, + pub head: Oid, + pub patches: usize, + pub issues: usize, +} + +pub fn router(ctx: Context) -> Router { + Router::new() + .route( + "/delegates/:delegate/projects", + get(delegates_projects_handler), + ) + .layer(Extension(ctx)) +} + +/// List all projects which delegate is a part of. +/// `GET /delegates/:delegate/projects` +async fn delegates_projects_handler( + Extension(ctx): Extension, + Path(delegate): Path, + Query(qs): Query, +) -> impl IntoResponse { + let PaginationQuery { page, per_page } = qs; + let page = page.unwrap_or(0); + let per_page = per_page.unwrap_or(10); + let storage = &ctx.profile.storage; + let projects = storage + .projects()? + .into_iter() + .filter_map(|id| { + let Ok(repo) = storage.repository(id) else { return None }; + let Ok((_, head)) = repo.head() else { return None }; + let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) else { return None }; + + if !delegates.iter().any(|d| d.id == delegate) { + return None; + } + + let Ok(cobs) = Store::open(ctx.profile.public_key, &repo) else { return None }; + let Ok(issues) = cobs.issues().count() else { return None }; + let Ok(patches) = cobs.patches().count() else { return None }; + + Some(Info { + payload, + head, + issues, + patches, + id, + }) + }) + .skip(page * per_page) + .take(per_page) + .collect::>(); + + Ok::<_, Error>(Json(projects)) +}