httpd: Add routes for patch ops
Signed-off-by: xphoniex <dj.2dixx@gmail.com>
This commit is contained in:
parent
88ffc02c5e
commit
4552c1f296
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::ops::Range;
|
|||
use std::str::FromStr;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -180,6 +181,10 @@ impl Patch {
|
|||
.timestamp
|
||||
}
|
||||
|
||||
pub fn tags(&self) -> impl Iterator<Item = &Tag> {
|
||||
self.tags.iter()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<&str> {
|
||||
Some(self.description.get().get())
|
||||
}
|
||||
|
|
@ -375,6 +380,10 @@ impl Revision {
|
|||
pub fn description(&self) -> &str {
|
||||
self.description.get()
|
||||
}
|
||||
|
||||
pub fn reviews(&self) -> impl DoubleEndedIterator<Item = (&PublicKey, &Review)> {
|
||||
self.reviews.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
|
|
@ -479,6 +488,19 @@ pub struct Review {
|
|||
pub timestamp: Max<Timestamp>,
|
||||
}
|
||||
|
||||
impl Serialize for Review {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Review", 3)?;
|
||||
state.serialize_field("verdict", &self.verdict())?;
|
||||
state.serialize_field("comment", &self.comment())?;
|
||||
state.serialize_field("timestamp", &self.timestamp())?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Semilattice for Review {
|
||||
fn merge(&mut self, other: Self) {
|
||||
self.verdict.merge(other.verdict);
|
||||
|
|
|
|||
Loading…
Reference in New Issue