Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
httpd: Add `/api/v1/profile` route to get entire profile
Sebastian Martinez committed 2 years ago
commit 9b985426924bcc60b3ae88916bf2639c0056a9a1
parent 8e0e1c232d14459ec19ada8cd937624880877710
3 files changed +124 -1
modified radicle-httpd/src/api/error.rs
@@ -95,7 +95,7 @@ impl IntoResponse for Error {
            Error::CobStore(e @ radicle::cob::store::Error::NotFound(_, _)) => {
                (StatusCode::NOT_FOUND, Some(e.to_string()))
            }
-
            Error::Auth(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
+
            Error::Auth(msg) => (StatusCode::UNAUTHORIZED, Some(msg.to_string())),
            Error::Crypto(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
            Error::Surf(radicle_surf::Error::Git(e)) if radicle::git::is_not_found_err(&e) => {
                (StatusCode::NOT_FOUND, Some(e.message().to_owned()))
modified radicle-httpd/src/api/v1.rs
@@ -1,5 +1,6 @@
mod delegates;
mod node;
+
mod profile;
mod projects;
mod sessions;
mod stats;
@@ -20,6 +21,7 @@ pub fn router(ctx: Context) -> Router {
    let routes = Router::new()
        .merge(root_router)
        .merge(node::router(ctx.clone()))
+
        .merge(profile::router(ctx.clone()))
        .merge(sessions::router(ctx.clone()))
        .merge(delegates::router(ctx.clone()))
        .merge(projects::router(ctx.clone()))
@@ -52,6 +54,11 @@ async fn root_handler(State(ctx): State<Context>) -> impl IntoResponse {
                "type": "GET"
            },
            {
+
                "href": "/profile",
+
                "rel": "profile",
+
                "type": "GET"
+
            },
+
            {
                "href": "/stats",
                "rel": "stats",
                "type": "GET"
added radicle-httpd/src/api/v1/profile.rs
@@ -0,0 +1,116 @@
+
use std::net::SocketAddr;
+

+
use axum::extract::{ConnectInfo, State};
+
use axum::response::IntoResponse;
+
use axum::routing::get;
+
use axum::{Json, Router};
+
use serde_json::json;
+

+
use crate::api::error::Error;
+
use crate::api::Context;
+

+
pub fn router(ctx: Context) -> Router {
+
    Router::new()
+
        .route("/profile", get(profile_handler))
+
        .with_state(ctx)
+
}
+

+
/// Return local profile information.
+
/// `GET /profile`
+
async fn profile_handler(
+
    State(ctx): State<Context>,
+
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
+
) -> impl IntoResponse {
+
    if !addr.ip().is_loopback() {
+
        return Err(Error::Auth("Profile data is only shown for localhost"));
+
    }
+

+
    Ok::<_, Error>(Json(
+
        json!({ "config": ctx.profile.config, "home": ctx.profile.home.path() }),
+
    ))
+
}
+

+
#[cfg(test)]
+
mod routes {
+
    use std::net::SocketAddr;
+

+
    use axum::extract::connect_info::MockConnectInfo;
+
    use axum::http::StatusCode;
+
    use serde_json::json;
+

+
    use crate::test::{self, get};
+

+
    #[tokio::test]
+
    async fn test_remote_profile() {
+
        let tmp = tempfile::tempdir().unwrap();
+
        let seed = test::seed(tmp.path());
+
        let app = super::router(seed.clone())
+
            .layer(MockConnectInfo(SocketAddr::from(([192, 168, 1, 1], 8080))));
+
        let response = get(&app, "/profile").await;
+

+
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
+
        assert_eq!(
+
            response.json().await,
+
            json!({
+
              "error": "Profile data is only shown for localhost",
+
              "code": 401
+
            })
+
        )
+
    }
+

+
    #[tokio::test]
+
    async fn test_profile() {
+
        let tmp = tempfile::tempdir().unwrap();
+
        let seed = test::seed(tmp.path());
+
        let app = super::router(seed.clone())
+
            .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))));
+
        let response = get(&app, "/profile").await;
+

+
        assert_eq!(response.status(), StatusCode::OK);
+
        assert_eq!(
+
            response.json().await,
+
            json!({
+
              "config": {
+
                "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid",
+
                "preferredSeeds": [
+
                  "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776"
+
                ],
+
                "cli": {
+
                  "hints": true
+
                },
+
                "node": {
+
                  "alias": "seed",
+
                  "listen": [],
+
                  "peers": {
+
                    "type": "dynamic",
+
                    "target": 8
+
                  },
+
                  "connect": [],
+
                  "externalAddresses": [],
+
                  "network": "main",
+
                  "relay": true,
+
                  "limits": {
+
                    "routingMaxSize": 1000,
+
                    "routingMaxAge": 604800,
+
                    "gossipMaxAge": 1209600,
+
                    "fetchConcurrency": 1,
+
                    "rate": {
+
                      "inbound": {
+
                        "fillRate": 0.2,
+
                        "capacity": 32
+
                      },
+
                      "outbound": {
+
                        "fillRate": 1.0,
+
                        "capacity": 64
+
                      }
+
                    }
+
                  },
+
                  "policy": "block",
+
                  "scope": "followed"
+
                }
+
              },
+
              "home": seed.profile.path()
+
            })
+
        );
+
    }
+
}