diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index d2ad2c73e..9048c1b9a 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -17,7 +17,7 @@ use tower_http::cors::{self, CorsLayer}; use radicle::cob::patch; use radicle::cob::{issue, Uri}; -use radicle::identity::Id; +use radicle::identity::{DocAt, Id}; use radicle::node::routing::Store; use radicle::storage::{ReadRepository, ReadStorage}; use radicle::Profile; @@ -54,7 +54,7 @@ impl Context { let storage = &self.profile.storage; let repo = storage.repository(id)?; let (_, head) = repo.head()?; - let doc = repo.identity_doc()?.1.verified()?; + let DocAt { doc, .. } = repo.identity_doc()?; let payload = doc.project()?; let delegates = doc.delegates; let issues = issue::Issues::open(&repo)?.counts()?; diff --git a/radicle-httpd/src/api/error.rs b/radicle-httpd/src/api/error.rs index 63535a47b..6a9696fb6 100644 --- a/radicle-httpd/src/api/error.rs +++ b/radicle-httpd/src/api/error.rs @@ -42,9 +42,9 @@ pub enum Error { #[error(transparent)] CobStore(#[from] radicle::cob::store::Error), - /// Identity error. + /// Repository error. #[error(transparent)] - Identity(#[from] radicle::identity::IdentityError), + Repository(#[from] radicle::storage::RepositoryError), /// Project doc error. #[error(transparent)] @@ -115,10 +115,7 @@ impl IntoResponse for Error { tracing::error!("Error: {message}"); if cfg!(debug_assertions) { - ( - StatusCode::INTERNAL_SERVER_ERROR, - Some(format!("{other:?}")), - ) + (StatusCode::INTERNAL_SERVER_ERROR, Some(other.to_string())) } else { (StatusCode::INTERNAL_SERVER_ERROR, None) } diff --git a/radicle-httpd/src/api/v1/delegates.rs b/radicle-httpd/src/api/v1/delegates.rs index 3a5e91195..aa584adf3 100644 --- a/radicle-httpd/src/api/v1/delegates.rs +++ b/radicle-httpd/src/api/v1/delegates.rs @@ -5,7 +5,7 @@ use axum::{Json, Router}; use radicle::cob::issue::Issues; use radicle::cob::patch::Patches; -use radicle::identity::Did; +use radicle::identity::{Did, DocAt}; use radicle::node::routing::Store; use radicle::storage::{ReadRepository, ReadStorage}; @@ -42,12 +42,10 @@ async fn delegates_projects_handler( .filter_map(|id| { let Ok(repo) = storage.repository(id) else { return None }; let Ok((_, head)) = repo.head() else { return None }; - let Ok((_, doc)) = repo.identity_doc() else { return None }; - let Ok(doc) = doc.verified() else { return None }; + let Ok(DocAt { doc, .. }) = repo.identity_doc() else { return None }; let Ok(payload) = doc.project() else { return None }; - let delegates = doc.delegates; - if !delegates.iter().any(|d| *d == delegate) { + if !doc.delegates.iter().any(|d| *d == delegate) { return None; } @@ -56,6 +54,7 @@ async fn delegates_projects_handler( let Ok(patches) = Patches::open(&repo) else { return None }; let Ok(patches) = patches.counts() else { return None }; + let delegates = doc.delegates; let trackings = routing.count(&id).unwrap_or_default(); Some(Info { diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs index c9c05959b..33e2a2a7f 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/projects.rs @@ -14,7 +14,7 @@ use serde_json::{json, Value}; use tower_http::set_header::SetResponseHeaderLayer; use radicle::cob::{issue, patch, Embed, Label, Uri}; -use radicle::identity::{Did, Id}; +use radicle::identity::{Did, DocAt, Id}; use radicle::node::routing::Store; use radicle::node::AliasStore; use radicle::node::NodeId; @@ -89,8 +89,7 @@ async fn project_root_handler( .filter_map(|id| { let Ok(repo) = storage.repository(id) else { return None }; let Ok((_, head)) = repo.head() else { return None }; - let Ok((_, doc)) = repo.identity_doc() else { return None }; - let Ok(doc) = doc.verified() else { return None }; + let Ok(DocAt { doc, .. }) = repo.identity_doc() else { return None }; let Ok(payload) = doc.project() else { return None }; let Ok(issues) = issue::Issues::open(&repo) else { return None }; let Ok(issues) = issues.counts() else { return None }; @@ -686,24 +685,16 @@ async fn issue_update_handler( api::auth::validate(&ctx, &token).await?; let storage = &ctx.profile.storage; - let signer = ctx.profile.signer().unwrap(); + let signer = ctx.profile.signer()?; let repo = storage.repository(project)?; let mut issues = issue::Issues::open(&repo)?; let mut issue = issues.get_mut(&issue_id.into())?; - match action { - issue::Action::Assign { assignees } => { - issue.assign(assignees, &signer)?; - } - issue::Action::Lifecycle { state } => { - issue.lifecycle(state, &signer)?; - } - issue::Action::Label { labels } => { - issue.label(labels, &signer)?; - } - issue::Action::Edit { title } => { - issue.edit(title, &signer)?; - } + let id = match action { + issue::Action::Assign { assignees } => issue.assign(assignees, &signer)?, + issue::Action::Lifecycle { state } => issue.lifecycle(state, &signer)?, + issue::Action::Label { labels } => issue.label(labels, &signer)?, + issue::Action::Edit { title } => issue.edit(title, &signer)?, issue::Action::Comment { body, reply_to, @@ -720,7 +711,7 @@ async fn issue_update_handler( }) .collect(); if let Some(to) = reply_to { - issue.comment(body, to, embeds, &signer)?; + issue.comment(body, to, embeds, &signer)? } else { return Err(Error::BadRequest("`replyTo` missing".to_owned())); } @@ -729,9 +720,7 @@ async fn issue_update_handler( id, reaction, active, - } => { - issue.react(id, reaction, active, &signer)?; - } + } => issue.react(id, reaction, active, &signer)?, issue::Action::CommentEdit { id, body, embeds } => { let embeds: Vec = embeds .into_iter() @@ -743,14 +732,12 @@ async fn issue_update_handler( }) }) .collect(); - issue.edit_comment(id, body, embeds, &signer)?; - } - issue::Action::CommentRedact { id } => { - issue.redact_comment(id, &signer)?; + issue.edit_comment(id, body, embeds, &signer)? } + issue::Action::CommentRedact { id } => issue.redact_comment(id, &signer)?, }; - Ok::<_, Error>(Json(json!({ "success": true }))) + Ok::<_, Error>(Json(json!({ "success": true, "id": id }))) } /// Get project issue. @@ -830,41 +817,27 @@ async fn patch_update_handler( let repo = storage.repository(project)?; let mut patches = patch::Patches::open(&repo)?; let mut patch = patches.get_mut(&patch_id.into())?; - match action { - patch::Action::Edit { title, target } => { - patch.edit(title, target, &signer)?; - } - patch::Action::Label { labels } => { - patch.label(labels, &signer)?; - } - patch::Action::Lifecycle { state } => { - patch.lifecycle(state, &signer)?; - } - patch::Action::Assign { assignees } => { - patch.assign(assignees, &signer)?; - } + let id = match action { + patch::Action::Edit { title, target } => patch.edit(title, target, &signer)?, + patch::Action::Label { labels } => patch.label(labels, &signer)?, + patch::Action::Lifecycle { state } => patch.lifecycle(state, &signer)?, + patch::Action::Assign { assignees } => patch.assign(assignees, &signer)?, patch::Action::Merge { revision, commit } => { // TODO: We should cleanup the stored copy at least. - let _ = patch.merge(revision, commit, &signer)?; + patch.merge(revision, commit, &signer)?.entry } patch::Action::Review { revision, summary, verdict, labels, - } => { - patch.review(revision, verdict, summary, labels, &signer)?; - } + } => *patch.review(revision, verdict, summary, labels, &signer)?, patch::Action::ReviewEdit { review, summary, verdict, - } => { - patch.edit_review(review, summary, verdict, &signer)?; - } - patch::Action::ReviewRedact { review } => { - patch.redact_review(review, &signer)?; - } + } => patch.edit_review(review, summary, verdict, &signer)?, + patch::Action::ReviewRedact { review } => patch.redact_review(review, &signer)?, patch::Action::ReviewComment { review, body, @@ -882,7 +855,7 @@ async fn patch_update_handler( }) }) .collect(); - patch.review_comment(review, body, location, reply_to, embeds, &signer)?; + patch.review_comment(review, body, location, reply_to, embeds, &signer)? } patch::Action::ReviewCommentEdit { review, @@ -900,42 +873,34 @@ async fn patch_update_handler( }) }) .collect(); - patch.edit_review_comment(review, comment, body, embeds, &signer)?; + patch.edit_review_comment(review, comment, body, embeds, &signer)? } patch::Action::ReviewCommentReact { review, comment, reaction, active, - } => { - patch.react_review_comment(review, comment, reaction, active, &signer)?; - } + } => patch.react_review_comment(review, comment, reaction, active, &signer)?, patch::Action::ReviewCommentRedact { review, comment } => { - patch.redact_review_comment(review, comment, &signer)?; + patch.redact_review_comment(review, comment, &signer)? } patch::Action::ReviewCommentResolve { review, comment } => { - patch.resolve_review_comment(review, comment, &signer)?; + patch.resolve_review_comment(review, comment, &signer)? } patch::Action::ReviewCommentUnresolve { review, comment } => { - patch.unresolve_review_comment(review, comment, &signer)?; + patch.unresolve_review_comment(review, comment, &signer)? } patch::Action::Revision { description, base, oid, .. - } => { - patch.update(description, base, oid, &signer)?; - } + } => patch.update(description, base, oid, &signer)?.into(), patch::Action::RevisionEdit { revision, description, - } => { - patch.edit_revision(revision, description, &signer)?; - } - patch::Action::RevisionRedact { revision } => { - patch.redact(revision, &signer)?; - } + } => patch.edit_revision(revision, description, &signer)?, + patch::Action::RevisionRedact { revision } => patch.redact(revision, &signer)?, patch::Action::RevisionComment { revision, body, @@ -953,7 +918,7 @@ async fn patch_update_handler( }) }) .collect(); - patch.comment(revision, body, reply_to, location, embeds, &signer)?; + patch.comment(revision, body, reply_to, location, embeds, &signer)? } patch::Action::RevisionCommentEdit { revision, @@ -971,25 +936,23 @@ async fn patch_update_handler( }) }) .collect(); - patch.comment_edit(revision, comment, body, embeds, &signer)?; + patch.comment_edit(revision, comment, body, embeds, &signer)? } patch::Action::RevisionCommentReact { revision, comment, reaction, active, - } => { - patch.comment_react(revision, comment, reaction, active, &signer)?; - } + } => patch.comment_react(revision, comment, reaction, active, &signer)?, patch::Action::RevisionCommentRedact { revision, comment } => { - patch.comment_redact(revision, comment, &signer)?; + patch.comment_redact(revision, comment, &signer)? } _ => { todo!(); } }; - Ok::<_, Error>(Json(json!({ "success": true }))) + Ok::<_, Error>(Json(json!({ "success": true, "id": id }))) } /// Get project patches list. @@ -2058,7 +2021,7 @@ mod routes { #[tokio::test] async fn test_projects_issues_create() { - const CREATED_ISSUE_ID: &str = "e712eb0b5874d5256022fb620f26caf847d96723"; + const CREATED_ISSUE_ID: &str = "8b42657072f6192cba9e08561582576a975656cd"; let tmp = tempfile::tempdir().unwrap(); let ctx = contributor(tmp.path()); @@ -2166,7 +2129,7 @@ mod routes { .await; assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.json().await, json!({ "success": true })); + assert_eq!(response.success().await, true); let body = serde_json::to_vec(&json!({ "type": "comment.react", @@ -2199,11 +2162,11 @@ mod routes { ) .await; - assert_eq!(response.json().await, json!({ "success": true })); + assert_eq!(response.success().await, true); let body = serde_json::to_vec(&json!({ "type": "comment.redact", - "id": "6fe9fdec2ec9f6436f2875dbcbedb95dd215b863", + "id": "918fff44966e4305523c077c80ac93b1196f1a7e", })) .unwrap(); @@ -2215,7 +2178,7 @@ mod routes { ) .await; - assert_eq!(response.json().await, json!({ "success": true })); + assert_eq!(response.success().await, true); let response = get( &app, @@ -2284,7 +2247,7 @@ mod routes { .await; assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.json().await, json!({ "success": true })); + assert_eq!(response.success().await, true); let response = get( &app, @@ -2423,7 +2386,7 @@ mod routes { #[tokio::test] async fn test_projects_create_patches() { - const CREATED_PATCH_ID: &str = "9cffd66099cceb0439a0f67c4aa99bde5e868eaa"; + const CREATED_PATCH_ID: &str = "beaed2e1d3b9b01ef10326a9a1c951799ba5fb25"; let tmp = tempfile::tempdir().unwrap(); let ctx = contributor(tmp.path()); @@ -2686,7 +2649,7 @@ mod routes { "reviews": [], }, { - "id": "b1f68feacb7040b089a77c1a0bff60a0411e6c1e", + "id": "341ba93c6db54e5891fbd3be4a4f64f4715681fa", "author": { "id": CONTRIBUTOR_DID, }, @@ -2857,10 +2820,12 @@ mod routes { .await; assert_eq!(response.status(), StatusCode::OK); + + let comment_id = response.id().await.to_string(); let comment_react_body = serde_json::to_vec(&json!({ "type": "revision.comment.react", "revision": CONTRIBUTOR_PATCH_ID, - "comment": CONTRIBUTOR_COMMENT_1, + "comment": comment_id, "reaction": "🚀", "active": true })) @@ -2876,7 +2841,7 @@ mod routes { let comment_edit = serde_json::to_vec(&json!({ "type": "revision.comment.edit", "revision": CONTRIBUTOR_PATCH_ID, - "comment": CONTRIBUTOR_COMMENT_1, + "comment": comment_id, "body": "EDIT: This is a root level comment", "embeds": [ { @@ -2899,7 +2864,7 @@ mod routes { "type": "revision.comment", "revision": CONTRIBUTOR_PATCH_ID, "body": "This is a root level comment", - "replyTo": CONTRIBUTOR_COMMENT_1, + "replyTo": comment_id, "embeds": [], })) .unwrap(); @@ -2912,6 +2877,7 @@ mod routes { .await; assert_eq!(response.status(), StatusCode::OK); + let comment_id_2 = response.id().await.to_string(); let response = get( &app, @@ -2946,7 +2912,7 @@ mod routes { ], "discussions": [ { - "id": CONTRIBUTOR_COMMENT_1, + "id": comment_id, "author": { "id": CONTRIBUTOR_DID, }, @@ -2963,7 +2929,7 @@ mod routes { "resolved": false, }, { - "id": CONTRIBUTOR_COMMENT_2, + "id": comment_id_2, "author": { "id": CONTRIBUTOR_DID, }, @@ -2971,7 +2937,7 @@ mod routes { "embeds": [], "reactions": [], "timestamp": TIMESTAMP, - "replyTo": CONTRIBUTOR_COMMENT_1, + "replyTo": comment_id, "resolved": false, }, ], @@ -3006,9 +2972,10 @@ mod routes { assert_eq!(response.status(), StatusCode::OK); + let review_id = response.id().await.to_string(); let review_comment_body = serde_json::to_vec(&json!({ "type": "review.comment", - "review": CONTRIBUTOR_PATCH_REVIEW, + "review": review_id, "body": "This is a comment on a review", "embeds": [], "location": { @@ -3023,7 +2990,7 @@ mod routes { } })) .unwrap(); - patch( + let response = patch( &app, format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"), Some(Body::from(review_comment_body)), @@ -3031,10 +2998,11 @@ mod routes { ) .await; + let comment_id = response.id().await.to_string(); let review_comment_edit_body = serde_json::to_vec(&json!({ "type": "review.comment.edit", - "review": CONTRIBUTOR_PATCH_REVIEW, - "comment": CONTRIBUTOR_COMMENT_3, + "review": review_id, + "comment": comment_id, "embeds": [], "body": "EDIT: This is a comment on a review", })) @@ -3049,8 +3017,8 @@ mod routes { let review_react_body = serde_json::to_vec(&json!({ "type": "review.comment.react", - "review": CONTRIBUTOR_PATCH_REVIEW, - "comment": CONTRIBUTOR_COMMENT_3, + "review": review_id, + "comment": comment_id, "reaction": "🚀", "active": true })) @@ -3065,8 +3033,8 @@ mod routes { let review_resolve_body = serde_json::to_vec(&json!({ "type": "review.comment.resolve", - "review": CONTRIBUTOR_PATCH_REVIEW, - "comment": CONTRIBUTOR_COMMENT_3, + "review": review_id, + "comment": comment_id, })) .unwrap(); patch( @@ -3118,7 +3086,7 @@ mod routes { "verdict": "accept", "summary": "A small review", "comments": [[ - CONTRIBUTOR_COMMENT_3, + comment_id, { "author": CONTRIBUTOR_NID, "location": { diff --git a/radicle-httpd/src/lib.rs b/radicle-httpd/src/lib.rs index 83b138c80..050d4fd8c 100644 --- a/radicle-httpd/src/lib.rs +++ b/radicle-httpd/src/lib.rs @@ -143,6 +143,7 @@ pub mod logger { pub fn subscriber() -> impl tracing::Subscriber { tracing_subscriber::FmtSubscriber::builder() .with_target(false) + .with_max_level(tracing::Level::DEBUG) .finish() } } diff --git a/radicle-httpd/src/test.rs b/radicle-httpd/src/test.rs index c95ec271c..395c7eecf 100644 --- a/radicle-httpd/src/test.rs +++ b/radicle-httpd/src/test.rs @@ -34,20 +34,16 @@ pub const HEAD: &str = "e8c676b9e3b42308dc9d218b70faa5408f8e58ca"; pub const PARENT: &str = "ee8d6a29304623a78ebfa5eeed5af674d0e58f83"; pub const INITIAL_COMMIT: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7"; pub const DID: &str = "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"; -pub const ISSUE_ID: &str = "0b0b8ca3b75e109971f87d92c1a6c930e87484c6"; -pub const ISSUE_DISCUSSION_ID: &str = "7466975f0bef37b459887824a9655f3e78262522"; -pub const ISSUE_COMMENT_ID: &str = "24ee306c508cd731a8427612dbdd826209096f99"; +pub const ISSUE_ID: &str = "4f98396a1ac987af59ec069de9b80d9917b27050"; +pub const ISSUE_DISCUSSION_ID: &str = "ceafc6629ec8dc0a17644fb5a66726aaafc3ed1c"; +pub const ISSUE_COMMENT_ID: &str = "59d35e164a21502bc91ad3391ce49baa32ea6a74"; pub const SESSION_ID: &str = "u9MGAkkfkMOv0uDDB2WeUHBT7HbsO2Dy"; pub const TIMESTAMP: u64 = 1671125284; pub const CONTRIBUTOR_RID: &str = "rad:z4XaCmN3jLSeiMvW15YTDpNbDHFhG"; pub const CONTRIBUTOR_DID: &str = "did:key:z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"; pub const CONTRIBUTOR_NID: &str = "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"; -pub const CONTRIBUTOR_ISSUE_ID: &str = "7466975f0bef37b459887824a9655f3e78262522"; -pub const CONTRIBUTOR_PATCH_ID: &str = "e651ae5869a2c1ac8ad4f6deae4cc835656ffa25"; -pub const CONTRIBUTOR_PATCH_REVIEW: &str = "ee3eeba95f4ec418b3d0714e18e0d1ff605dc0e6"; -pub const CONTRIBUTOR_COMMENT_1: &str = "d8ff07edbc8d2229e54e70f2f5bc31614287f0dc"; -pub const CONTRIBUTOR_COMMENT_2: &str = "f3fca0add53f85bc51a85198efed3273fe13b88e"; -pub const CONTRIBUTOR_COMMENT_3: &str = "06990ff59faa12463f693dae7a98eb33d75afd2e"; +pub const CONTRIBUTOR_ISSUE_ID: &str = "ceafc6629ec8dc0a17644fb5a66726aaafc3ed1c"; +pub const CONTRIBUTOR_PATCH_ID: &str = "4ff2ec53a2d165da7f54705023e847d4f9230bc3"; /// Create a new profile. pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile { @@ -96,6 +92,8 @@ fn seed_with_signer(dir: &Path, profile: radicle::Profile, signer: &G let routing_db = dir.join("radicle").join("node").join("routing.db"); let addresses_db = dir.join("radicle").join("node").join("addresses.db"); + crate::logger::init().ok(); + TrackingStore::Config::open(tracking_db).unwrap(); RoutingStore::Table::open(routing_db).unwrap(); AddressStore::Book::open(addresses_db).unwrap(); @@ -191,7 +189,7 @@ fn seed_with_signer(dir: &Path, profile: radicle::Profile, signer: &G let storage = &profile.storage; let repo = storage.repository(id).unwrap(); let mut issues = Issues::open(&repo).unwrap(); - let _ = issues + let issue = issues .create( "Issue #1".to_string(), "Change 'hello world' to 'hello everyone'".to_string(), @@ -201,12 +199,13 @@ fn seed_with_signer(dir: &Path, profile: radicle::Profile, signer: &G signer, ) .unwrap(); + tracing::debug!(target: "test", "Contributor issue: {}", issue.id()); // eq. rad patch open let mut patches = Patches::open(&repo).unwrap(); let oid = radicle::git::Oid::from_str(HEAD).unwrap(); let base = radicle::git::Oid::from_str(PARENT).unwrap(); - let _ = patches + let patch = patches .create( "A new `hello world`", "change `hello world` in README to something else", @@ -217,6 +216,7 @@ fn seed_with_signer(dir: &Path, profile: radicle::Profile, signer: &G signer, ) .unwrap(); + tracing::debug!(target: "test", "Contributor patch: {}", patch.id()); let options = crate::Options { aliases: std::collections::HashMap::new(), @@ -318,10 +318,24 @@ pub struct Response(axum::response::Response); impl Response { pub async fn json(self) -> Value { - let body = hyper::body::to_bytes(self.0.into_body()).await.unwrap(); + let body = self.body().await; serde_json::from_slice(&body).unwrap() } + pub async fn id(self) -> radicle::git::Oid { + let json = self.json().await; + let string = json["id"].as_str().unwrap(); + + radicle::git::Oid::from_str(string).unwrap() + } + + pub async fn success(self) -> bool { + let json = self.json().await; + let success = json["success"].as_bool(); + + success.unwrap_or(false) + } + pub fn status(&self) -> axum::http::StatusCode { self.0.status() }