Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
httpd: Add routes for patch ops
xphoniex committed 3 years ago
commit 49590bc72d0a23849e01f1bb05d8931a00fe1ef5
parent a8a24ec94546aa8ff01ea82fe7f9f20e3153ed6d
3 files changed +145 -0
modified radicle-httpd/src/api/json.rs
@@ -4,6 +4,7 @@ use std::path::Path;

use serde_json::{json, Value};

+
use radicle::cob::patch::{Patch, PatchId};
use radicle_surf::blob::Blob;
use radicle_surf::tree::Tree;
use radicle_surf::{Commit, Stats};
@@ -61,6 +62,26 @@ pub(crate) fn tree(tree: &Tree, path: &str, stats: &Stats) -> Value {
    })
}

+
/// Returns JSON for a `patch`.
+
pub(crate) fn patch(id: PatchId, patch: Patch) -> Value {
+
    json!({
+
        "id": id.to_string(),
+
        "author": patch.author(),
+
        "title": patch.title(),
+
        "description": patch.description(),
+
        "state": patch.state(),
+
        "target": patch.target(),
+
        "tags": patch.tags().collect::<Vec<_>>(),
+
        "revisions": patch.revisions().map(|(id, rev)| {
+
            json!({
+
                "id": id,
+
                "description": rev.description(),
+
                "reviews": rev.reviews().collect::<Vec<_>>(),
+
            })
+
        }).collect::<Vec<_>>(),
+
    })
+
}
+

/// Returns the name part of a path string.
fn name_in_path(path: &str) -> &str {
    match path.rsplit('/').next() {
modified radicle-httpd/src/api/test.rs
@@ -1,4 +1,5 @@
use std::path::Path;
+
use std::str::FromStr;
use std::sync::Arc;
use std::{env, fs};

@@ -9,6 +10,7 @@ use serde_json::Value;
use tower::ServiceExt;

use radicle::cob::issue::Issues;
+
use radicle::cob::patch::{MergeTarget, Patches};
use radicle::git::raw as git2;
use radicle::storage::WriteStorage;
use radicle_cli::commands::rad_init;
@@ -110,6 +112,22 @@ pub fn seed(dir: &Path) -> Context {
        )
        .unwrap();

+
    // eq. rad patch open
+
    let mut patches = Patches::open(*signer.public_key(), &repo).unwrap();
+
    let oid = radicle::git::Oid::from_str(HEAD).unwrap();
+
    let base = radicle::git::Oid::from_str(HEAD_1).unwrap();
+
    patches
+
        .create(
+
            "A new `hello word`",
+
            "change `hello world` in README to something else",
+
            MergeTarget::Delegates,
+
            base,
+
            oid,
+
            &[],
+
            &signer,
+
        )
+
        .unwrap();
+

    Context {
        profile: Arc::new(profile),
        sessions: Default::default(),
modified radicle-httpd/src/api/v1/projects.rs
@@ -12,6 +12,7 @@ use serde_json::json;
use tower_http::set_header::SetResponseHeaderLayer;

use radicle::cob::issue::Issues;
+
use radicle::cob::patch::Patches;
use radicle::cob::thread::{self, CommentId};
use radicle::cob::Timestamp;
use radicle::identity::{Id, PublicKey};
@@ -49,6 +50,8 @@ pub fn router(ctx: Context) -> Router {
        .route("/projects/:project/readme/:sha", get(readme_handler))
        .route("/projects/:project/issues", get(issues_handler))
        .route("/projects/:project/issues/:id", get(issue_handler))
+
        .route("/projects/:project/patches", get(patches_handler))
+
        .route("/projects/:project/patches/:id", get(patch_handler))
        .with_state(ctx)
}

@@ -422,6 +425,46 @@ async fn issue_handler(
    Ok::<_, Error>(Json(issue))
}

+
/// Get project patches list.
+
/// `GET /projects/:project/patches`
+
async fn patches_handler(
+
    State(ctx): State<Context>,
+
    Path(project): Path<Id>,
+
    Query(qs): Query<PaginationQuery>,
+
) -> 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 repo = storage.repository(project)?;
+
    let patches = Patches::open(ctx.profile.public_key, &repo)?;
+
    let patches = patches
+
        .all()?
+
        .into_iter()
+
        .filter_map(|r| r.ok())
+
        .map(|(id, patch, _)| api::json::patch(id, patch))
+
        .skip(page * per_page)
+
        .take(per_page)
+
        .collect::<Vec<_>>();
+

+
    Ok::<_, Error>(Json(patches))
+
}
+

+
/// Get project patch.
+
/// `GET /projects/:project/patches/:id`
+
async fn patch_handler(
+
    State(ctx): State<Context>,
+
    Path((project, patch_id)): Path<(Id, Oid)>,
+
) -> impl IntoResponse {
+
    let storage = &ctx.profile.storage;
+
    let repo = storage.repository(project)?;
+
    let patch = Patches::open(ctx.profile.public_key, &repo)?
+
        .get(&patch_id.into())?
+
        .ok_or(Error::NotFound)?;
+

+
    Ok::<_, Error>(Json(api::json::patch(patch_id.into(), patch)))
+
}
+

#[derive(Serialize)]
struct Author {
    id: PublicKey,
@@ -939,4 +982,67 @@ mod routes {
            ])
        );
    }
+

+
    #[tokio::test]
+
    async fn test_projects_patches() {
+
        let tmp = tempfile::tempdir().unwrap();
+
        let app = super::router(test::seed(tmp.path()));
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/patches").await;
+

+
        assert_eq!(response.status(), StatusCode::OK);
+
        assert_eq!(
+
            response.json().await,
+
            json!([
+
              {
+
                "id": "5de9f17ca5326258412ab02f9a5339b6482198ce",
+
                "author": {
+
                    "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+
                },
+
                "title": "A new `hello word`",
+
                "description": "change `hello world` in README to something else",
+
                "state": "proposed",
+
                "target": "delegates",
+
                "tags": [],
+
                "revisions": [
+
                    {
+
                        "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/1",
+
                        "description": "",
+
                        "reviews": [],
+
                    }
+
                ],
+
              }
+
            ])
+
        );
+

+
        let response = get(
+
            &app,
+
            "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/patches/5de9f17ca5326258412ab02f9a5339b6482198ce",
+
        )
+
        .await;
+

+
        assert_eq!(response.status(), StatusCode::OK);
+
        assert_eq!(
+
            response.json().await,
+
            json!(
+
              {
+
                "id": "5de9f17ca5326258412ab02f9a5339b6482198ce",
+
                "author": {
+
                    "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+
                },
+
                "title": "A new `hello word`",
+
                "description": "change `hello world` in README to something else",
+
                "state": "proposed",
+
                "target": "delegates",
+
                "tags": [],
+
                "revisions": [
+
                    {
+
                        "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/1",
+
                        "description": "",
+
                        "reviews": [],
+
                    }
+
                ],
+
              }
+
            )
+
        );
+
    }
}