diff --git a/.gitignore b/.gitignore index 26610ba0c..c2e63eb8f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules/ # Tauri +crates/test-http-api/target crates/radicle-tauri/target crates/radicle-tauri/gen/schemas diff --git a/Cargo.lock b/Cargo.lock index efb18a772..76bd6feff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,7 +292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", - "axum-core", + "axum-core 0.3.4", "bitflags 1.3.2", "bytes", "futures-util", @@ -308,7 +308,40 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 0.1.2", - "tower", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-util", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower 0.5.1", "tower-layer", "tower-service", ] @@ -330,6 +363,26 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -1114,8 +1167,8 @@ dependencies = [ "tonic", "tonic-health", "tonic-web", - "tower", - "tower-http", + "tower 0.4.13", + "tower-http 0.4.4", "tower-layer", "tracing", "tracing-core", @@ -2164,6 +2217,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "httparse", + "httpdate", "itoa 1.0.11", "pin-project-lite", "smallvec", @@ -2537,6 +2591,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "lexopt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" + [[package]] name = "libappindicator" version = "0.9.0" @@ -3992,7 +4052,6 @@ version = "0.0.0" dependencies = [ "anyhow", "base64 0.22.1", - "localtime", "log", "radicle", "radicle-surf", @@ -4014,6 +4073,9 @@ dependencies = [ name = "radicle-types" version = "0.1.0" dependencies = [ + "anyhow", + "base64 0.22.1", + "localtime", "radicle", "radicle-surf", "serde", @@ -4506,6 +4568,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa 1.0.11", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -5382,6 +5454,24 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-http-api" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.7", + "hyper 1.5.0", + "lexopt", + "radicle", + "radicle-surf", + "radicle-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower-http 0.5.2", +] + [[package]] name = "thin-slice" version = "0.1.1" @@ -5603,7 +5693,7 @@ checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", - "axum", + "axum 0.6.20", "base64 0.21.7", "bytes", "h2", @@ -5616,7 +5706,7 @@ dependencies = [ "prost", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5649,7 +5739,7 @@ dependencies = [ "pin-project", "tokio-stream", "tonic", - "tower-http", + "tower-http 0.4.4", "tower-layer", "tower-service", "tracing", @@ -5675,6 +5765,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.4.4" @@ -5693,6 +5798,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 19f5a06e0..c99ad65da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,6 @@ resolver = "1" members = [ "crates/radicle-tauri", - "crates/radicle-types" + "crates/radicle-types", + "crates/test-http-api", ] diff --git a/crates/radicle-tauri/Cargo.toml b/crates/radicle-tauri/Cargo.toml index 352b8c7a8..be6edb38d 100644 --- a/crates/radicle-tauri/Cargo.toml +++ b/crates/radicle-tauri/Cargo.toml @@ -18,7 +18,6 @@ tauri-build = { version = "2.0.1", features = ["isolation"] } anyhow = { version = "1.0.90" } base64 = { version = "0.22.1" } log = { version = "0.4.22" } -localtime = { version = "1.3.1" } radicle = { git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git" } radicle-types = { version = "0.1.0", path = "../radicle-types" } radicle-surf = { version = "0.22.1", features = ["serde"] } diff --git a/crates/radicle-tauri/src/commands/auth.rs b/crates/radicle-tauri/src/commands/auth.rs index 6e1f2a950..07f356f97 100644 --- a/crates/radicle-tauri/src/commands/auth.rs +++ b/crates/radicle-tauri/src/commands/auth.rs @@ -1,28 +1,8 @@ -use anyhow::anyhow; - -use radicle::crypto::ssh; +use radicle_types::traits::auth::Auth; use crate::{error::Error, AppState}; #[tauri::command] -pub fn authenticate(ctx: tauri::State) -> Result<(), Error> { - let profile = &ctx.profile; - - if !profile.keystore.is_encrypted()? { - return Ok(()); - } - match ssh::agent::Agent::connect() { - Ok(mut agent) => { - if agent.request_identities()?.contains(&profile.public_key) { - Ok(()) - } else { - Err(Error::WithHint { - err: anyhow!("Not able to find your keys in the ssh agent"), - hint: "Make sure to run rad auth in your terminal to add your keys to the ssh-agent.", - })? - } - } - Err(e) if e.is_not_running() => Err(Error::WithHint { err: anyhow!("SSH Agent is not running"), hint: "For now we require the user to have an ssh agent running, since we don't have passphrase inputs yet." })?, - Err(e) => Err(e)?, - } +pub(crate) fn authenticate(ctx: tauri::State) -> Result<(), Error> { + ctx.authenticate().map_err(Error::from) } diff --git a/crates/radicle-tauri/src/commands/cob.rs b/crates/radicle-tauri/src/commands/cob.rs index b1aba901a..786a44715 100644 --- a/crates/radicle-tauri/src/commands/cob.rs +++ b/crates/radicle-tauri/src/commands/cob.rs @@ -1,10 +1,9 @@ -use base64::{engine::general_purpose::STANDARD, Engine as _}; - use radicle::cob; use radicle::git; use radicle::identity; -use radicle::storage::{ReadRepository, ReadStorage}; use radicle_types as types; +use radicle_types::traits::cobs::Cobs; +use radicle_types::traits::thread::Thread; use crate::{error, AppState}; @@ -18,10 +17,7 @@ pub async fn get_file_by_oid( rid: identity::RepoId, oid: git::Oid, ) -> Result { - let repo = ctx.profile.storage.repository(rid)?; - let blob = repo.blob(oid)?; - - Ok::<_, error::Error>(STANDARD.encode(blob.content())) + ctx.get_embed(rid, oid).map_err(error::Error::from) } #[tauri::command] @@ -31,10 +27,7 @@ pub async fn save_embed( name: &str, bytes: &[u8], ) -> Result { - let repo = ctx.profile.storage.repository(rid)?; - let embed = cob::Embed::::store(name, bytes, &repo.backend)?; - - Ok::<_, error::Error>(embed.oid()) + ctx.save_embed(rid, name, bytes).map_err(error::Error::from) } #[tauri::command] @@ -44,82 +37,6 @@ pub fn activity_by_id( type_name: cob::TypeName, id: git::Oid, ) -> Result, error::Error> { - let aliases = ctx.profile.aliases(); - let repo = ctx.profile.storage.repository(rid)?; - let ops = cob::store::ops(&id.into(), &type_name, &repo).unwrap(); - let mut actions: Vec = Vec::new(); - - for op in ops.into_iter() { - actions.extend(op.actions.iter().filter_map( - |action: &Vec| -> Option { - let action: types::cobs::issue::Action = serde_json::from_slice(action).ok()?; - - Some(types::cobs::issue::Operation { - entry_id: op.id, - action, - author: types::cobs::Author::new(op.author.into(), &aliases), - timestamp: op.timestamp, - }) - }, - )) - } - - Ok::<_, error::Error>(actions) -} - -mod query { - use serde::{Deserialize, Serialize}; - - use radicle::issue; - use radicle::patch; - - #[derive(Default, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub enum IssueStatus { - Closed, - #[default] - Open, - All, - } - - impl IssueStatus { - pub fn matches(&self, issue: &issue::State) -> bool { - match self { - Self::Open => matches!(issue, issue::State::Open), - Self::Closed => matches!(issue, issue::State::Closed { .. }), - Self::All => true, - } - } - } - - #[derive(Default, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub enum PatchStatus { - #[default] - Open, - Draft, - Archived, - Merged, - } - - impl From for PatchStatus { - fn from(value: patch::Status) -> Self { - match value { - patch::Status::Archived => Self::Archived, - patch::Status::Draft => Self::Draft, - patch::Status::Merged => Self::Merged, - patch::Status::Open => Self::Open, - } - } - } - impl From for patch::Status { - fn from(value: PatchStatus) -> Self { - match value { - PatchStatus::Archived => Self::Archived, - PatchStatus::Draft => Self::Draft, - PatchStatus::Merged => Self::Merged, - PatchStatus::Open => Self::Open, - } - } - } + ctx.activity_by_id(rid, type_name, id) + .map_err(error::Error::from) } diff --git a/crates/radicle-tauri/src/commands/cob/draft.rs b/crates/radicle-tauri/src/commands/cob/draft.rs index 0d8f44c03..a3ae8b7fe 100644 --- a/crates/radicle-tauri/src/commands/cob/draft.rs +++ b/crates/radicle-tauri/src/commands/cob/draft.rs @@ -1,10 +1,8 @@ use radicle::cob; -use radicle::cob::object::Storage; use radicle::git; -use radicle::git::refs::storage::draft; use radicle::identity; -use radicle::storage; -use radicle::storage::ReadStorage; + +use radicle_types::traits::cobs::Cobs; use crate::error::Error; use crate::AppState; @@ -20,26 +18,6 @@ pub fn publish_draft( cob_id: git::Oid, type_name: cob::TypeName, ) -> Result<(), Error> { - let signer = ctx.profile.signer()?; - let repo = ctx.profile.storage.repository(rid)?; - let draft_oid = - repo.backend - .refname_to_id(&draft::cob(signer.public_key(), &type_name, &cob_id.into()))?; - repo.update( - signer.public_key(), - &type_name, - &cob_id.into(), - &draft_oid.into(), - )?; - - let mut patches = ctx.profile.patches_mut(&repo)?; - patches.write(&cob_id.into())?; - - storage::git::cob::DraftStore::new(&repo, *signer.public_key()).remove( - signer.public_key(), - &type_name, - &cob_id.into(), - )?; - - Ok::<_, Error>(()) + ctx.publish_draft(rid, cob_id, type_name) + .map_err(Error::from) } diff --git a/crates/radicle-tauri/src/commands/cob/issue.rs b/crates/radicle-tauri/src/commands/cob/issue.rs index da04a4944..6b5e17c23 100644 --- a/crates/radicle-tauri/src/commands/cob/issue.rs +++ b/crates/radicle-tauri/src/commands/cob/issue.rs @@ -1,12 +1,10 @@ use radicle::git; use radicle::identity; -use radicle::issue::cache::Issues; -use radicle::node::Handle; -use radicle::node::Node; -use radicle::storage::ReadStorage; -use radicle_types as types; -use crate::cob::query; +use radicle_types as types; +use radicle_types::traits::issue::Issues; +use radicle_types::traits::issue::IssuesMut; + use crate::error::Error; use crate::AppState; @@ -17,25 +15,7 @@ pub fn create_issue( new: types::cobs::issue::NewIssue, opts: types::cobs::CobOptions, ) -> Result { - let mut node = Node::new(ctx.profile.socket()); - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let aliases = ctx.profile.aliases(); - let mut issues = ctx.profile.issues_mut(&repo)?; - let issue = issues.create( - new.title, - new.description, - &new.labels, - &new.assignees, - new.embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - - if opts.announce() { - node.announce_refs(rid)?; - } - - Ok::<_, Error>(types::cobs::issue::Issue::new(issue.id(), &issue, &aliases)) + ctx.create_issue(rid, new, opts).map_err(Error::from) } #[tauri::command] @@ -46,103 +26,24 @@ pub fn edit_issue( action: types::cobs::issue::Action, opts: types::cobs::CobOptions, ) -> Result { - let mut node = Node::new(ctx.profile.socket()); - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let aliases = ctx.profile.aliases(); - let mut issues = ctx.profile.issues_mut(&repo)?; - let mut issue = issues.get_mut(&cob_id.into())?; - - match action { - types::cobs::issue::Action::Lifecycle { state } => { - issue.lifecycle(state.into(), &signer)?; - } - types::cobs::issue::Action::Assign { assignees } => { - issue.assign(assignees, &signer)?; - } - types::cobs::issue::Action::Label { labels } => { - issue.label(labels, &signer)?; - } - types::cobs::issue::Action::CommentReact { - id, - reaction, - active, - } => { - issue.react(id, reaction, active, &signer)?; - } - types::cobs::issue::Action::CommentRedact { id } => { - issue.redact_comment(id, &signer)?; - } - types::cobs::issue::Action::Comment { - body, - reply_to, - embeds, - } => { - issue.comment( - body, - reply_to.unwrap_or(cob_id), - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::issue::Action::CommentEdit { id, body, embeds } => { - issue.edit_comment( - id, - body, - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::issue::Action::Edit { title } => { - issue.edit(title, &signer)?; - } - } - - if opts.announce() { - node.announce_refs(rid)?; - } - - Ok::<_, Error>(types::cobs::issue::Issue::new(issue.id(), &issue, &aliases)) + ctx.edit_issue(rid, cob_id, action, opts) + .map_err(Error::from) } #[tauri::command] -pub fn list_issues( +pub(crate) fn list_issues( ctx: tauri::State, rid: identity::RepoId, - status: query::IssueStatus, + status: Option, ) -> Result, Error> { - let repo = ctx.profile.storage.repository(rid)?; - let issues = ctx.profile.issues(&repo)?; - let mut issues: Vec<_> = issues - .list()? - .filter_map(|r| { - let (id, issue) = r.ok()?; - (status.matches(issue.state())).then_some((id, issue)) - }) - .collect::>(); - - issues.sort_by(|(_, a), (_, b)| b.timestamp().cmp(&a.timestamp())); - let aliases = &ctx.profile.aliases(); - let issues = issues - .into_iter() - .map(|(id, issue)| types::cobs::issue::Issue::new(&id, &issue, aliases)) - .collect::>(); - - Ok::<_, Error>(issues) + ctx.list_issues(rid, status).map_err(Error::from) } #[tauri::command] -pub fn issue_by_id( +pub(crate) fn issue_by_id( ctx: tauri::State, rid: identity::RepoId, id: git::Oid, ) -> Result, Error> { - let repo = ctx.profile.storage.repository(rid)?; - let issues = ctx.profile.issues(&repo)?; - let issue = issues.get(&id.into())?; - - let aliases = &ctx.profile.aliases(); - let issue = issue.map(|issue| types::cobs::issue::Issue::new(&id.into(), &issue, aliases)); - - Ok::<_, Error>(issue) + ctx.issue_by_id(rid, id).map_err(Error::from) } diff --git a/crates/radicle-tauri/src/commands/cob/patch.rs b/crates/radicle-tauri/src/commands/cob/patch.rs index a14da2ad8..b3e0822c1 100644 --- a/crates/radicle-tauri/src/commands/cob/patch.rs +++ b/crates/radicle-tauri/src/commands/cob/patch.rs @@ -1,15 +1,12 @@ use radicle::cob; use radicle::git; use radicle::identity; -use radicle::node::Handle; -use radicle::node::Node; use radicle::patch; -use radicle::patch::cache::Patches; -use radicle::storage; -use radicle::storage::ReadStorage; -use radicle_types as types; -use crate::cob::query; +use radicle_types as types; +use radicle_types::traits::patch::Patches; +use radicle_types::traits::patch::PatchesMut; + use crate::error::Error; use crate::AppState; @@ -17,38 +14,12 @@ use crate::AppState; pub async fn list_patches( ctx: tauri::State<'_, AppState>, rid: identity::RepoId, - status: Option, + status: Option, skip: Option, take: Option, ) -> Result>, Error> { - let cursor = skip.unwrap_or(0); - let take = take.unwrap_or(20); - let repo = ctx.profile.storage.repository(rid)?; - let aliases = &ctx.profile.aliases(); - let cache = ctx.profile.patches(&repo)?; - let patches = match status { - None => cache.list()?.collect::>(), - Some(s) => cache.list_by_status(&s.into())?.collect::>(), - }; - let more = cursor + take < patches.len(); - - let mut patches = patches - .into_iter() - .filter_map(|p| { - p.map(|(id, patch)| types::cobs::patch::Patch::new(id, &patch, aliases)) - .ok() - }) - .skip(cursor) - .take(take) - .collect::>(); - - patches.sort_by_key(|b| std::cmp::Reverse(b.timestamp())); - - Ok::<_, Error>(types::cobs::PaginatedQuery { - cursor, - more, - content: patches, - }) + ctx.list_patches(rid, status, skip, take) + .map_err(Error::from) } #[tauri::command] @@ -57,13 +28,7 @@ pub fn patch_by_id( rid: identity::RepoId, id: git::Oid, ) -> Result, Error> { - let repo = ctx.profile.storage.repository(rid)?; - let patches = ctx.profile.patches(&repo)?; - let patch = patches.get(&id.into())?; - let aliases = &ctx.profile.aliases(); - let patches = patch.map(|patch| types::cobs::patch::Patch::new(id.into(), &patch, aliases)); - - Ok::<_, Error>(patches) + ctx.get_patch(rid, id).map_err(Error::from) } #[tauri::command] @@ -72,18 +37,7 @@ pub fn revisions_by_patch( rid: identity::RepoId, id: git::Oid, ) -> Result>, Error> { - let repo = ctx.profile.storage.repository(rid)?; - let patches = ctx.profile.patches(&repo)?; - let revisions = patches.get(&id.into())?.map(|patch| { - let aliases = &ctx.profile.aliases(); - - patch - .revisions() - .map(|(_, r)| types::cobs::patch::Revision::new(r.clone(), aliases)) - .collect::>() - }); - - Ok::<_, Error>(revisions) + ctx.revisions_by_patch(rid, id).map_err(Error::from) } #[tauri::command] @@ -93,23 +47,10 @@ pub fn revision_by_patch_and_id( id: git::Oid, revision_id: git::Oid, ) -> Result, Error> { - let repo = ctx.profile.storage.repository(rid)?; - let patches = ctx.profile.patches(&repo)?; - let revision = patches.get(&id.into())?.and_then(|patch| { - let aliases = &ctx.profile.aliases(); - - patch - .revision(&revision_id.into()) - .map(|r| types::cobs::patch::Revision::new(r.clone(), aliases)) - }); - - Ok::<_, Error>(revision) + ctx.revision_by_id(rid, id, revision_id) + .map_err(Error::from) } -/// Creates a draft review for a specific patch revision. -/// -/// This Tauri command allows users to create a new draft review for a specific patch revision. -/// The draft is associated with the user (signer) and the provided patch revision within the repository. #[tauri::command] pub fn create_draft_review( ctx: tauri::State, @@ -118,37 +59,8 @@ pub fn create_draft_review( cob_id: git::Oid, labels: Vec, ) -> Result { - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); - - let mut patches = cob::patch::Cache::no_cache(&drafts)?; - let mut patch = patches.get_mut(&cob_id.into())?; - let revision = patch - .revision(&revision_id) - .ok_or_else(|| Error::WithHint { - err: anyhow::anyhow!("patch revision not found"), - hint: "Not able to find the specified patch revision.", - })?; - - revision - .review_by(signer.public_key()) - .ok_or(Error::WithHint { - err: anyhow::anyhow!("duplicate patch review found"), - hint: "Found an existing draft patch review on this patch revision and repo.", - })?; - - let review_id = patch.review( - revision.id(), - Some(cob::patch::Verdict::Reject), - None, - labels, - &signer, - )?; - - patches.write(&cob_id.into())?; - - Ok::<_, Error>(review_id) + ctx.create_draft_review(rid, revision_id, cob_id, labels) + .map_err(Error::from) } /// Creates a new review comment on a draft review for a specific patch. @@ -163,28 +75,8 @@ pub fn create_draft_review_comment( cob_id: git::Oid, new: types::cobs::thread::CreateReviewComment, ) -> Result<(), Error> { - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); - - let mut patches = cob::patch::Cache::no_cache(&drafts)?; - let mut patch = patches.get_mut(&cob_id.into())?; - - patch.transaction("Review comments", &signer, |tx| { - tx.review_comment( - new.review_id, - new.body, - new.location.map(|l| l.into()), - new.reply_to, - new.embeds.into_iter().map(|e| e.into()).collect::>(), - )?; - - Ok(()) - })?; - - patches.write(&cob_id.into())?; - - Ok::<_, Error>(()) + ctx.create_draft_review_comment(rid, cob_id, new) + .map_err(Error::from) } /// Edits a draft review for a specific patch revision in a repository. @@ -198,31 +90,10 @@ pub fn edit_draft_review( cob_id: git::Oid, edit: types::cobs::patch::ReviewEdit, ) -> Result<(), Error> { - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); - - let mut patches = cob::patch::Cache::no_cache(&drafts)?; - let mut patch = patches.get_mut(&cob_id.into())?; - patch.review_edit( - edit.review_id, - edit.verdict, - edit.summary, - edit.labels, - &signer, - )?; - - patches.write(&cob_id.into())?; - - Ok::<_, Error>(()) + ctx.edit_draft_review(rid, cob_id, edit) + .map_err(Error::from) } -/// Gets the draft review of the local user for a specific patch revision in a repository. -/// -/// This Tauri command is used to retrieve a patch review draft for the local user -/// on a given patch revision from a repository. -/// It looks up the repository using the provided repository ID (`rid`) and patch object ID (`cob_id`), -/// and gets the patch review of the local user associated with a specific revision (`revision_id`), if it exists. #[tauri::command] pub fn get_draft_review( ctx: tauri::State, @@ -230,16 +101,7 @@ pub fn get_draft_review( cob_id: git::Oid, revision_id: patch::RevisionId, ) -> Option { - let repo = ctx.profile.storage.repository(rid).ok()?; - let signer = ctx.profile.signer().ok()?; - let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); - let patches = cob::patch::Cache::no_cache(&drafts).ok()?; - - let patch = patches.get(&cob_id.into()).ok()?; - let revision = patch.and_then(|p| p.revision(&revision_id).cloned()); - let review = revision.and_then(|rev| rev.review_by(signer.public_key()).cloned()); - - review + ctx.get_draft_review(rid, cob_id, revision_id) } #[tauri::command] @@ -250,177 +112,6 @@ pub fn edit_patch( action: types::cobs::patch::Action, opts: types::cobs::CobOptions, ) -> Result { - let mut node = Node::new(ctx.profile.socket()); - let repo = ctx.profile.storage.repository(rid)?; - let signer = ctx.profile.signer()?; - let aliases = ctx.profile.aliases(); - let mut patches = ctx.profile.patches_mut(&repo)?; - let mut patch = patches.get_mut(&cob_id.into())?; - - match action { - types::cobs::patch::Action::RevisionEdit { - revision, - description, - embeds, - } => { - patch.edit_revision( - revision, - description, - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::patch::Action::RevisionCommentRedact { revision, comment } => { - patch.comment_redact(revision, comment, &signer)?; - } - types::cobs::patch::Action::ReviewCommentRedact { review, comment } => { - patch.redact_review_comment(review, comment, &signer)?; - } - types::cobs::patch::Action::ReviewCommentReact { - review, - comment, - reaction, - active, - } => { - patch.react_review_comment(review, comment, reaction, active, &signer)?; - } - types::cobs::patch::Action::ReviewCommentResolve { review, comment } => { - patch.resolve_review_comment(review, comment, &signer)?; - } - types::cobs::patch::Action::ReviewCommentUnresolve { review, comment } => { - patch.unresolve_review_comment(review, comment, &signer)?; - } - types::cobs::patch::Action::Edit { title, target } => { - patch.edit(title, target, &signer)?; - } - types::cobs::patch::Action::ReviewEdit { - review, - summary, - verdict, - labels, - } => { - patch.review_edit(review, verdict, summary, labels, &signer)?; - } - types::cobs::patch::Action::Review { - revision, - summary, - verdict, - labels, - } => { - patch.review(revision, verdict, summary, labels, &signer)?; - } - types::cobs::patch::Action::ReviewRedact { review } => { - patch.redact_review(review, &signer)?; - } - types::cobs::patch::Action::ReviewComment { - review, - body, - location, - reply_to, - embeds, - } => { - patch.review_comment( - review, - body, - location.map(|l| l.into()), - reply_to, - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::patch::Action::ReviewCommentEdit { - review, - comment, - body, - embeds, - } => { - patch.edit_review_comment( - review, - comment, - body, - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::patch::Action::Lifecycle { state } => { - patch.lifecycle(state, &signer)?; - } - types::cobs::patch::Action::Assign { assignees } => { - patch.assign(assignees, &signer)?; - } - types::cobs::patch::Action::Label { labels } => { - patch.label(labels, &signer)?; - } - types::cobs::patch::Action::RevisionReact { - revision, - reaction, - location, - active, - } => { - patch.react( - revision, - reaction, - location.map(|l| l.into()), - active, - &signer, - )?; - } - types::cobs::patch::Action::RevisionComment { - revision, - location, - body, - reply_to, - embeds, - } => { - patch.comment( - revision, - body, - reply_to, - location.map(|l| l.into()), - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::patch::Action::RevisionCommentEdit { - revision, - comment, - body, - embeds, - } => { - patch.comment_edit( - revision, - comment, - body, - embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - } - types::cobs::patch::Action::RevisionCommentReact { - revision, - comment, - reaction, - active, - } => { - patch.comment_react(revision, comment, reaction, active, &signer)?; - } - types::cobs::patch::Action::RevisionRedact { revision } => { - patch.redact(revision, &signer)?; - } - types::cobs::patch::Action::Merge { .. } => { - unimplemented!("We don't support merging of patches through the desktop") - } - types::cobs::patch::Action::Revision { .. } => { - unimplemented!("We don't support creating new revisions through the desktop") - } - } - - if opts.announce() { - node.announce_refs(rid)?; - } - - Ok::<_, Error>(types::cobs::patch::Patch::new( - *patch.id(), - &patch, - &aliases, - )) + ctx.edit_patch(rid, cob_id, action, opts) + .map_err(Error::from) } diff --git a/crates/radicle-tauri/src/commands/diff.rs b/crates/radicle-tauri/src/commands/diff.rs index 207f04b6f..0ef291bfb 100644 --- a/crates/radicle-tauri/src/commands/diff.rs +++ b/crates/radicle-tauri/src/commands/diff.rs @@ -1,45 +1,15 @@ -use radicle::storage::ReadStorage; use radicle_surf as surf; -use radicle::git; use radicle::identity; -use serde::Deserialize; -use serde::Serialize; +use radicle_types::traits::repo::Repo; use crate::{error, AppState}; -#[derive(Serialize, Deserialize)] -pub struct Options { - pub base: git::Oid, - pub head: git::Oid, - pub unified: u32, -} - #[tauri::command] pub async fn get_diff( ctx: tauri::State<'_, AppState>, rid: identity::RepoId, - options: Options, + options: radicle_types::cobs::diff::Options, ) -> Result { - let repo = ctx.profile.storage.repository(rid)?.backend; - let base = repo.find_commit(*options.base)?; - let head = repo.find_commit(*options.head)?; - - let mut opts = git::raw::DiffOptions::new(); - opts.patience(true) - .minimal(true) - .context_lines(options.unified); - - let mut find_opts = git::raw::DiffFindOptions::new(); - find_opts.exact_match_only(true); - find_opts.all(true); - - let left = base.tree()?; - let right = head.tree()?; - - let mut diff = repo.diff_tree_to_tree(Some(&left), Some(&right), Some(&mut opts))?; - diff.find_similar(Some(&mut find_opts))?; - let diff = surf::diff::Diff::try_from(diff)?; - - Ok::<_, error::Error>(diff) + ctx.get_diff(rid, options).map_err(error::Error::from) } diff --git a/crates/radicle-tauri/src/commands/profile.rs b/crates/radicle-tauri/src/commands/profile.rs index a0f9b61df..b4468c442 100644 --- a/crates/radicle-tauri/src/commands/profile.rs +++ b/crates/radicle-tauri/src/commands/profile.rs @@ -1,15 +1,9 @@ use radicle_types::config::Config; +use radicle_types::traits::Profile; -use crate::error::Error; use crate::AppState; #[tauri::command] -pub fn config(ctx: tauri::State) -> Result { - let config = Config { - public_key: ctx.profile.public_key, - alias: ctx.profile.config.node.alias.clone(), - seeding_policy: ctx.profile.config.node.seeding_policy, - }; - - Ok::<_, Error>(config) +pub fn config(ctx: tauri::State) -> Config { + ctx.config() } diff --git a/crates/radicle-tauri/src/commands/repo.rs b/crates/radicle-tauri/src/commands/repo.rs index 50844c2ee..c1ffaa87c 100644 --- a/crates/radicle-tauri/src/commands/repo.rs +++ b/crates/radicle-tauri/src/commands/repo.rs @@ -1,40 +1,15 @@ -use radicle::crypto::Verified; -use radicle::identity::doc::PayloadId; -use radicle::identity::{DocAt, RepoId}; -use radicle::issue::cache::Issues; -use radicle::node::routing::Store; -use radicle::patch::cache::Patches; -use radicle::prelude::Doc; -use radicle::storage::git::Repository; -use radicle::storage::ReadStorage; -use radicle::storage::{self, ReadRepository}; +use radicle::git; +use radicle::identity::RepoId; + use radicle_types as types; +use radicle_types::traits::repo::Repo; use crate::error::Error; use crate::AppState; #[tauri::command] pub fn list_repos(ctx: tauri::State) -> Result, Error> { - let storage = &ctx.profile.storage; - let policies = ctx.profile.policies()?; - let mut repos = storage.repositories()?.into_iter().collect::>(); - repos.sort_by_key(|p| p.rid); - - let infos = repos - .into_iter() - .filter_map(|info| { - if !policies.is_seeding(&info.rid).unwrap_or_default() { - return None; - } - let repo = ctx.profile.storage.repository(info.rid).ok()?; - let DocAt { doc, .. } = repo.identity_doc().ok()?; - let repo_info = repo_info(&ctx.profile, &repo, &doc).ok()?; - - Some(repo_info) - }) - .collect::>(); - - Ok::<_, Error>(infos) + ctx.list_repos().map_err(Error::from) } #[tauri::command] @@ -42,72 +17,15 @@ pub fn repo_by_id( ctx: tauri::State, rid: RepoId, ) -> Result { - let repo = ctx.profile.storage.repository(rid)?; - let DocAt { doc, .. } = repo.identity_doc()?; - - let repo_info = repo_info(&ctx.profile, &repo, &doc)?; - - Ok::<_, Error>(repo_info) + ctx.repo_by_id(rid).map_err(Error::from) } #[tauri::command] pub async fn diff_stats( ctx: tauri::State<'_, AppState>, rid: RepoId, - base: String, - head: String, + base: git::Oid, + head: git::Oid, ) -> Result { - let repo = radicle_surf::Repository::open(storage::git::paths::repository( - &ctx.profile.storage, - &rid, - ))?; - let base = repo.commit(base)?; - let commit = repo.commit(head)?; - let diff = repo.diff(base.id, commit.id)?; - let stats = diff.stats(); - - Ok::<_, Error>(types::cobs::Stats::new(stats)) -} - -pub fn repo_info( - profile: &radicle::Profile, - repo: &Repository, - doc: &Doc, -) -> Result { - let aliases = profile.aliases(); - let delegates = doc - .delegates - .clone() - .into_iter() - .map(|did| types::cobs::Author::new(did, &aliases)) - .collect::>(); - let db = profile.database()?; - let seeding = db.count(&repo.id).unwrap_or_default(); - let project = doc.payload.get(&PayloadId::project()).and_then(|payload| { - let (_, head) = repo.head().ok()?; - let commit = repo.commit(head).ok()?; - let patches = profile.patches(repo).ok()?; - let patches = patches.counts().ok()?; - let issues = profile.issues(repo).ok()?; - let issues = issues.counts().ok()?; - - let data: types::repo::ProjectPayloadData = (*payload).clone().try_into().ok()?; - let meta = types::repo::ProjectPayloadMeta { - issues, - patches, - head, - last_commit_timestamp: commit.time().seconds() * 1000, - }; - - Some(types::repo::ProjectPayload::new(data, meta)) - }); - - Ok::<_, Error>(types::repo::RepoInfo { - payloads: types::repo::SupportedPayloads { project }, - delegates, - threshold: doc.threshold, - visibility: doc.visibility.clone().into(), - rid: repo.id, - seeding, - }) + ctx.diff_stats(rid, base, head).map_err(Error::from) } diff --git a/crates/radicle-tauri/src/commands/thread.rs b/crates/radicle-tauri/src/commands/thread.rs index a8667e309..f1ff354b3 100644 --- a/crates/radicle-tauri/src/commands/thread.rs +++ b/crates/radicle-tauri/src/commands/thread.rs @@ -1,11 +1,7 @@ -use localtime::LocalTime; - -use radicle::cob; use radicle::identity; -use radicle::node::Handle; -use radicle::storage::ReadStorage; -use radicle::Node; + use radicle_types as types; +use radicle_types::traits::thread::Thread; use crate::error::Error; use crate::AppState; @@ -17,40 +13,8 @@ pub fn create_issue_comment( new: types::cobs::thread::NewIssueComment, opts: types::cobs::CobOptions, ) -> Result, Error> { - let aliases = &ctx.profile.aliases(); - let mut node = Node::new(ctx.profile.socket()); - let signer = ctx.profile.signer()?; - let repo = ctx.profile.storage.repository(rid)?; - let mut issues = ctx.profile.issues_mut(&repo)?; - let mut issue = issues.get_mut(&new.id.into())?; - let id = new.reply_to.unwrap_or_else(|| { - let (root_id, _) = issue.root(); - *root_id - }); - let n = new.clone(); - let oid = issue.comment( - n.body, - id, - n.embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - - if opts.announce() { - node.announce_refs(rid)?; - } - - Ok(types::cobs::thread::Comment::::new( - oid, - cob::thread::Comment::new( - *signer.public_key(), - new.body, - id.into(), - None, - new.embeds.into_iter().map(|e| e.into()).collect::>(), - LocalTime::now().into(), - ), - aliases, - )) + ctx.create_issue_comment(rid, new, opts) + .map_err(Error::from) } #[tauri::command] @@ -60,38 +24,6 @@ pub fn create_patch_comment( new: types::cobs::thread::NewPatchComment, opts: types::cobs::CobOptions, ) -> Result, Error> { - let aliases = &ctx.profile.aliases(); - let mut node = Node::new(ctx.profile.socket()); - let signer = ctx.profile.signer()?; - let repo = ctx.profile.storage.repository(rid)?; - let mut patches = ctx.profile.patches_mut(&repo)?; - let mut patch = patches.get_mut(&new.id.into())?; - let n = new.clone(); - let oid = patch.comment( - new.revision.into(), - n.body, - n.reply_to, - n.location.map(|l| l.into()), - n.embeds.into_iter().map(|e| e.into()).collect::>(), - &signer, - )?; - - if opts.announce() { - node.announce_refs(rid)?; - } - - Ok(types::cobs::thread::Comment::< - types::cobs::thread::CodeLocation, - >::new( - oid, - cob::thread::Comment::new( - *signer.public_key(), - new.body, - new.reply_to, - new.location.map(|l| l.into()), - new.embeds.into_iter().map(|e| e.into()).collect::>(), - LocalTime::now().into(), - ), - aliases, - )) + ctx.create_patch_comment(rid, new, opts) + .map_err(Error::from) } diff --git a/crates/radicle-tauri/src/error.rs b/crates/radicle-tauri/src/error.rs index 584abd68a..a5400fe6f 100644 --- a/crates/radicle-tauri/src/error.rs +++ b/crates/radicle-tauri/src/error.rs @@ -51,6 +51,10 @@ pub enum Error { #[error(transparent)] Storage(#[from] radicle::storage::Error), + /// Types error. + #[error(transparent)] + Types(#[from] radicle_types::error::Error), + /// Surf error. #[error(transparent)] Surf(#[from] radicle_surf::Error), diff --git a/crates/radicle-tauri/src/lib.rs b/crates/radicle-tauri/src/lib.rs index ea2a03ae4..896f43576 100644 --- a/crates/radicle-tauri/src/lib.rs +++ b/crates/radicle-tauri/src/lib.rs @@ -6,6 +6,13 @@ use tauri::Manager; use radicle::node::Handle; use radicle::Node; +use radicle_types::traits::auth::Auth; +use radicle_types::traits::cobs::Cobs; +use radicle_types::traits::issue::{Issues, IssuesMut}; +use radicle_types::traits::patch::{Patches, PatchesMut}; +use radicle_types::traits::repo::Repo; +use radicle_types::traits::thread::Thread; +use radicle_types::traits::Profile; use commands::{auth, cob, diff, profile, repo, thread}; @@ -13,6 +20,20 @@ struct AppState { profile: radicle::Profile, } +impl Auth for AppState {} +impl Repo for AppState {} +impl Thread for AppState {} +impl Cobs for AppState {} +impl Issues for AppState {} +impl IssuesMut for AppState {} +impl Patches for AppState {} +impl PatchesMut for AppState {} +impl Profile for AppState { + fn profile(&self) -> radicle::Profile { + self.profile.clone() + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { #[cfg(debug_assertions)] diff --git a/crates/radicle-types/Cargo.toml b/crates/radicle-types/Cargo.toml index 1aa686fec..546160c04 100644 --- a/crates/radicle-types/Cargo.toml +++ b/crates/radicle-types/Cargo.toml @@ -4,9 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = { version = "1.0.90" } +base64 = { version = "0.22.1" } radicle = { git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git" } radicle-surf = { version = "0.22.1", features = ["serde"] } serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.132" } thiserror = { version = "1.0.65" } -ts-rs = { version = "10.0.0", features = ["serde-json-impl", "no-serde-warnings"] } +ts-rs = { version = "10.0.0", features = [ "serde-json-impl", "no-serde-warnings" ] } +localtime = { version = "1.3.1" } diff --git a/crates/radicle-types/src/cobs.rs b/crates/radicle-types/src/cobs.rs index 54cdf27e5..5894d785e 100644 --- a/crates/radicle-types/src/cobs.rs +++ b/crates/radicle-types/src/cobs.rs @@ -4,11 +4,12 @@ use ts_rs::TS; use radicle::identity; use radicle::node::{Alias, AliasStore}; +pub mod diff; pub mod issue; pub mod patch; pub mod thread; -#[derive(Serialize, TS)] +#[derive(Debug, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] #[ts(export_to = "cob/")] @@ -78,3 +79,60 @@ impl Stats { } } } + +pub mod query { + use serde::{Deserialize, Serialize}; + + use radicle::issue; + use radicle::patch; + + #[derive(Default, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum IssueStatus { + Closed, + #[default] + Open, + All, + } + + impl IssueStatus { + pub fn matches(&self, issue: &issue::State) -> bool { + match self { + Self::Open => matches!(issue, issue::State::Open), + Self::Closed => matches!(issue, issue::State::Closed { .. }), + Self::All => true, + } + } + } + + #[derive(Default, Serialize, Deserialize, Clone)] + #[serde(rename_all = "camelCase")] + pub enum PatchStatus { + #[default] + Open, + Draft, + Archived, + Merged, + } + + impl From for PatchStatus { + fn from(value: patch::Status) -> Self { + match value { + patch::Status::Archived => Self::Archived, + patch::Status::Draft => Self::Draft, + patch::Status::Merged => Self::Merged, + patch::Status::Open => Self::Open, + } + } + } + impl From for patch::Status { + fn from(value: PatchStatus) -> Self { + match value { + PatchStatus::Archived => Self::Archived, + PatchStatus::Draft => Self::Draft, + PatchStatus::Merged => Self::Merged, + PatchStatus::Open => Self::Open, + } + } + } +} diff --git a/crates/radicle-types/src/cobs/diff.rs b/crates/radicle-types/src/cobs/diff.rs new file mode 100644 index 000000000..b3e51e799 --- /dev/null +++ b/crates/radicle-types/src/cobs/diff.rs @@ -0,0 +1,9 @@ +use radicle::git; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Options { + pub base: git::Oid, + pub head: git::Oid, + pub unified: u32, +} diff --git a/crates/radicle-types/src/cobs/patch.rs b/crates/radicle-types/src/cobs/patch.rs index 7c03d4378..b1a306ee9 100644 --- a/crates/radicle-types/src/cobs/patch.rs +++ b/crates/radicle-types/src/cobs/patch.rs @@ -12,7 +12,7 @@ use radicle::patch; use crate::cobs; -#[derive(TS, Serialize)] +#[derive(Debug, TS, Serialize)] #[ts(export)] #[ts(export_to = "cob/patch/")] #[serde(rename_all = "camelCase")] @@ -57,7 +57,7 @@ impl Patch { } } -#[derive(Serialize, Deserialize, TS)] +#[derive(Debug, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", tag = "status")] #[ts(export)] #[ts(export_to = "cob/patch/")] diff --git a/crates/radicle-types/src/error.rs b/crates/radicle-types/src/error.rs index 2aca92b86..a4f83f541 100644 --- a/crates/radicle-types/src/error.rs +++ b/crates/radicle-types/src/error.rs @@ -2,11 +2,88 @@ use serde::Serialize; #[derive(Debug, thiserror::Error)] pub enum Error { + /// Profile error. + #[error(transparent)] + Profile(#[from] radicle::profile::Error), + + /// Crypto error. + #[error(transparent)] + Crypto(#[from] radicle::crypto::ssh::keystore::Error), + + /// SSH Agent error. + #[error(transparent)] + Agent(#[from] radicle::crypto::ssh::agent::Error), + + /// Node database error. + #[error(transparent)] + Database(#[from] radicle::node::db::Error), + + /// Repository error. + #[error(transparent)] + Repository(#[from] radicle::storage::RepositoryError), + + /// Policy store error. + #[error(transparent)] + PolicyStore(#[from] radicle::node::policy::store::Error), + + /// Cob patch cache error. + #[error(transparent)] + CachePatch(#[from] radicle::cob::patch::cache::Error), + + /// Diff error. + #[error(transparent)] + Diff(#[from] radicle_surf::diff::git::error::Diff), + + /// Storage error. + #[error(transparent)] + Storage(#[from] radicle::storage::Error), + + /// Radicle Git error. + #[error(transparent)] + Git(#[from] radicle::git::Error), + + /// Surf error. + #[error(transparent)] + Surf(#[from] radicle_surf::Error), + + /// Git2 error. + #[error(transparent)] + Git2(#[from] radicle::git::raw::Error), + + /// Cob issue cache error. + #[error(transparent)] + CacheIssue(#[from] radicle::cob::issue::cache::Error), + + /// Patch error. + #[error(transparent)] + Patch(#[from] radicle::patch::Error), + + /// Issue error. + #[error(transparent)] + Issue(#[from] radicle::issue::Error), + + /// Node error. + #[error(transparent)] + Node(#[from] radicle::node::Error), + + /// An error with a hint. + #[error("{err} {hint}")] + WithHint { + err: anyhow::Error, + hint: &'static str, + }, + /// Serde JSON error. #[error(transparent)] SerdeJSON(#[from] serde_json::error::Error), } +#[derive(Serialize)] +struct ErrorWrapperWithHint { + err: String, + hint: String, +} + #[derive(Serialize)] struct ErrorWrapper { err: String, @@ -17,6 +94,15 @@ impl Serialize for Error { where S: serde::ser::Serializer, { + if let Error::WithHint { err, hint } = self { + let error_wrapper = ErrorWrapperWithHint { + err: err.to_string(), + hint: hint.to_string(), + }; + + return error_wrapper.serialize(serializer); + } + let wrapper = ErrorWrapper { err: self.to_string(), }; diff --git a/crates/radicle-types/src/lib.rs b/crates/radicle-types/src/lib.rs index f0f019413..87e3758ca 100644 --- a/crates/radicle-types/src/lib.rs +++ b/crates/radicle-types/src/lib.rs @@ -2,3 +2,4 @@ pub mod cobs; pub mod config; pub mod error; pub mod repo; +pub mod traits; diff --git a/crates/radicle-types/src/traits.rs b/crates/radicle-types/src/traits.rs new file mode 100644 index 000000000..8ead8766e --- /dev/null +++ b/crates/radicle-types/src/traits.rs @@ -0,0 +1,22 @@ +use crate::config::Config; + +pub mod auth; +pub mod cobs; +pub mod issue; +pub mod patch; +pub mod repo; +pub mod thread; + +pub trait Profile { + fn profile(&self) -> radicle::Profile; + + fn config(&self) -> Config { + let p = self.profile(); + + Config { + public_key: p.public_key, + alias: p.config.node.alias.clone(), + seeding_policy: p.config.node.seeding_policy, + } + } +} diff --git a/crates/radicle-types/src/traits/auth.rs b/crates/radicle-types/src/traits/auth.rs new file mode 100644 index 000000000..74135137e --- /dev/null +++ b/crates/radicle-types/src/traits/auth.rs @@ -0,0 +1,31 @@ +use anyhow::anyhow; + +use radicle::crypto::ssh; + +use crate::error::Error; + +use super::Profile; + +pub trait Auth: Profile { + fn authenticate(&self) -> Result<(), Error> { + let profile = &self.profile(); + + if !profile.keystore.is_encrypted()? { + return Ok(()); + } + match ssh::agent::Agent::connect() { + Ok(mut agent) => { + if agent.request_identities()?.contains(&profile.public_key) { + Ok(()) + } else { + Err(Error::WithHint { + err: anyhow!("Not able to find your keys in the ssh agent"), + hint: "Make sure to run rad auth in your terminal to add your keys to the ssh-agent.", + })? + } + } + Err(e) if e.is_not_running() => Err(Error::WithHint { err: anyhow!("SSH Agent is not running"), hint: "For now we require the user to have an ssh agent running, since we don't have passphrase inputs yet." })?, + Err(e) => Err(e)?, + } + } +} diff --git a/crates/radicle-types/src/traits/cobs.rs b/crates/radicle-types/src/traits/cobs.rs new file mode 100644 index 000000000..af2cc3cd8 --- /dev/null +++ b/crates/radicle-types/src/traits/cobs.rs @@ -0,0 +1,72 @@ +use radicle::cob::object::Storage; +use radicle::storage::refs::draft; +use radicle::storage::{self, ReadStorage}; +use radicle::{cob, git, identity}; + +use crate::error::Error; +use crate::traits::Profile; + +pub trait Cobs: Profile { + fn activity_by_id( + &self, + rid: identity::RepoId, + type_name: cob::TypeName, + id: git::Oid, + ) -> Result, Error> { + let profile = self.profile(); + let aliases = profile.aliases(); + let repo = profile.storage.repository(rid)?; + let ops = cob::store::ops(&id.into(), &type_name, &repo).unwrap(); + let mut actions: Vec = Vec::new(); + + for op in ops.into_iter() { + actions.extend(op.actions.iter().filter_map( + |action: &Vec| -> Option { + let action: crate::cobs::issue::Action = serde_json::from_slice(action).ok()?; + + Some(crate::cobs::issue::Operation { + entry_id: op.id, + action, + author: crate::cobs::Author::new(op.author.into(), &aliases), + timestamp: op.timestamp, + }) + }, + )) + } + + Ok::<_, Error>(actions) + } + + fn publish_draft( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + type_name: cob::TypeName, + ) -> Result<(), Error> { + let profile = self.profile(); + let signer = profile.signer()?; + let repo = profile.storage.repository(rid)?; + let draft_oid = repo.backend.refname_to_id(&draft::cob( + signer.public_key(), + &type_name, + &cob_id.into(), + ))?; + repo.update( + signer.public_key(), + &type_name, + &cob_id.into(), + &draft_oid.into(), + )?; + + let mut patches = profile.patches_mut(&repo)?; + patches.write(&cob_id.into())?; + + storage::git::cob::DraftStore::new(&repo, *signer.public_key()).remove( + signer.public_key(), + &type_name, + &cob_id.into(), + )?; + + Ok::<_, Error>(()) + } +} diff --git a/crates/radicle-types/src/traits/issue.rs b/crates/radicle-types/src/traits/issue.rs new file mode 100644 index 000000000..0bec2271b --- /dev/null +++ b/crates/radicle-types/src/traits/issue.rs @@ -0,0 +1,150 @@ +use radicle::issue::cache::Issues as _; +use radicle::node::Handle; +use radicle::storage::ReadStorage; +use radicle::{git, identity, Node}; + +use crate::cobs; +use crate::error::Error; +use crate::traits::Profile; + +pub trait Issues: Profile { + fn list_issues( + &self, + rid: identity::RepoId, + status: Option, + ) -> Result, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let status = status.unwrap_or_default(); + let issues = profile.issues(&repo)?; + let mut issues: Vec<_> = issues + .list()? + .filter_map(|r| { + let (id, issue) = r.ok()?; + (status.matches(issue.state())).then_some((id, issue)) + }) + .collect::>(); + + issues.sort_by(|(_, a), (_, b)| b.timestamp().cmp(&a.timestamp())); + let aliases = &profile.aliases(); + let issues = issues + .into_iter() + .map(|(id, issue)| cobs::issue::Issue::new(&id, &issue, aliases)) + .collect::>(); + + Ok::<_, Error>(issues) + } + + fn issue_by_id( + &self, + rid: identity::RepoId, + id: git::Oid, + ) -> Result, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let issues = profile.issues(&repo)?; + let issue = issues.get(&id.into())?; + + let aliases = &profile.aliases(); + let issue = issue.map(|issue| cobs::issue::Issue::new(&id.into(), &issue, aliases)); + + Ok::<_, Error>(issue) + } +} + +pub trait IssuesMut: Profile { + fn create_issue( + &self, + rid: identity::RepoId, + new: cobs::issue::NewIssue, + opts: cobs::CobOptions, + ) -> Result { + let profile = self.profile(); + let mut node = Node::new(profile.socket()); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let aliases = profile.aliases(); + let mut issues = profile.issues_mut(&repo)?; + let issue = issues.create( + new.title, + new.description, + &new.labels, + &new.assignees, + new.embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok::<_, Error>(cobs::issue::Issue::new(issue.id(), &issue, &aliases)) + } + + fn edit_issue( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + action: cobs::issue::Action, + opts: cobs::CobOptions, + ) -> Result { + let profile = self.profile(); + let mut node = Node::new(profile.socket()); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let aliases = profile.aliases(); + let mut issues = profile.issues_mut(&repo)?; + let mut issue = issues.get_mut(&cob_id.into())?; + + match action { + cobs::issue::Action::Lifecycle { state } => { + issue.lifecycle(state.into(), &signer)?; + } + cobs::issue::Action::Assign { assignees } => { + issue.assign(assignees, &signer)?; + } + cobs::issue::Action::Label { labels } => { + issue.label(labels, &signer)?; + } + cobs::issue::Action::CommentReact { + id, + reaction, + active, + } => { + issue.react(id, reaction, active, &signer)?; + } + cobs::issue::Action::CommentRedact { id } => { + issue.redact_comment(id, &signer)?; + } + cobs::issue::Action::Comment { + body, + reply_to, + embeds, + } => { + issue.comment( + body, + reply_to.unwrap_or(cob_id), + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::issue::Action::CommentEdit { id, body, embeds } => { + issue.edit_comment( + id, + body, + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::issue::Action::Edit { title } => { + issue.edit(title, &signer)?; + } + } + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok::<_, Error>(cobs::issue::Issue::new(issue.id(), &issue, &aliases)) + } +} diff --git a/crates/radicle-types/src/traits/patch.rs b/crates/radicle-types/src/traits/patch.rs new file mode 100644 index 000000000..20fe69356 --- /dev/null +++ b/crates/radicle-types/src/traits/patch.rs @@ -0,0 +1,421 @@ +use radicle::node::Handle; +use radicle::patch::cache::Patches as _; +use radicle::storage; +use radicle::storage::ReadStorage; +use radicle::{cob, git, identity, patch, Node}; + +use crate::cobs; +use crate::error::Error; +use crate::traits::Profile; + +pub trait Patches: Profile { + fn list_patches( + &self, + rid: identity::RepoId, + status: Option, + skip: Option, + take: Option, + ) -> Result>, Error> { + let profile = self.profile(); + let cursor = skip.unwrap_or(0); + let take = take.unwrap_or(20); + let repo = profile.storage.repository(rid)?; + let aliases = &profile.aliases(); + let cache = profile.patches(&repo)?; + let patches = match status { + None => cache.list()?.collect::>(), + Some(s) => cache.list_by_status(&s.into())?.collect::>(), + }; + let more = cursor + take < patches.len(); + + let mut patches = patches + .into_iter() + .filter_map(|p| { + p.map(|(id, patch)| cobs::patch::Patch::new(id, &patch, aliases)) + .ok() + }) + .skip(cursor) + .take(take) + .collect::>(); + + patches.sort_by_key(|b| std::cmp::Reverse(b.timestamp())); + + Ok::<_, Error>(cobs::PaginatedQuery { + cursor, + more, + content: patches, + }) + } + + fn get_patch( + &self, + rid: identity::RepoId, + id: git::Oid, + ) -> Result, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let patches = profile.patches(&repo)?; + let patch = patches.get(&id.into())?; + let aliases = &profile.aliases(); + let patches = patch.map(|patch| cobs::patch::Patch::new(id.into(), &patch, aliases)); + + Ok::<_, Error>(patches) + } + + fn revisions_by_patch( + &self, + rid: identity::RepoId, + id: git::Oid, + ) -> Result>, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let patches = profile.patches(&repo)?; + let revisions = patches.get(&id.into())?.map(|patch| { + let aliases = &profile.aliases(); + + patch + .revisions() + .map(|(_, r)| cobs::patch::Revision::new(r.clone(), aliases)) + .collect::>() + }); + + Ok::<_, Error>(revisions) + } + + fn revision_by_id( + &self, + rid: identity::RepoId, + id: git::Oid, + revision_id: git::Oid, + ) -> Result, Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let patches = profile.patches(&repo)?; + let revision = patches.get(&id.into())?.and_then(|patch| { + let aliases = &profile.aliases(); + + patch + .revision(&revision_id.into()) + .map(|r| cobs::patch::Revision::new(r.clone(), aliases)) + }); + + Ok::<_, Error>(revision) + } +} + +pub trait PatchesMut: Profile { + fn edit_patch( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + action: cobs::patch::Action, + opts: cobs::CobOptions, + ) -> Result { + let profile = self.profile(); + let mut node = Node::new(profile.socket()); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let aliases = profile.aliases(); + let mut patches = profile.patches_mut(&repo)?; + let mut patch = patches.get_mut(&cob_id.into())?; + + match action { + cobs::patch::Action::RevisionEdit { + revision, + description, + embeds, + } => { + patch.edit_revision( + revision, + description, + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::patch::Action::RevisionCommentRedact { revision, comment } => { + patch.comment_redact(revision, comment, &signer)?; + } + cobs::patch::Action::ReviewCommentRedact { review, comment } => { + patch.redact_review_comment(review, comment, &signer)?; + } + cobs::patch::Action::ReviewCommentReact { + review, + comment, + reaction, + active, + } => { + patch.react_review_comment(review, comment, reaction, active, &signer)?; + } + cobs::patch::Action::ReviewCommentResolve { review, comment } => { + patch.resolve_review_comment(review, comment, &signer)?; + } + cobs::patch::Action::ReviewCommentUnresolve { review, comment } => { + patch.unresolve_review_comment(review, comment, &signer)?; + } + cobs::patch::Action::Edit { title, target } => { + patch.edit(title, target, &signer)?; + } + cobs::patch::Action::ReviewEdit { + review, + summary, + verdict, + labels, + } => { + patch.review_edit(review, verdict, summary, labels, &signer)?; + } + cobs::patch::Action::Review { + revision, + summary, + verdict, + labels, + } => { + patch.review(revision, verdict, summary, labels, &signer)?; + } + cobs::patch::Action::ReviewRedact { review } => { + patch.redact_review(review, &signer)?; + } + cobs::patch::Action::ReviewComment { + review, + body, + location, + reply_to, + embeds, + } => { + patch.review_comment( + review, + body, + location.map(|l| l.into()), + reply_to, + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::patch::Action::ReviewCommentEdit { + review, + comment, + body, + embeds, + } => { + patch.edit_review_comment( + review, + comment, + body, + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::patch::Action::Lifecycle { state } => { + patch.lifecycle(state, &signer)?; + } + cobs::patch::Action::Assign { assignees } => { + patch.assign(assignees, &signer)?; + } + cobs::patch::Action::Label { labels } => { + patch.label(labels, &signer)?; + } + cobs::patch::Action::RevisionReact { + revision, + reaction, + location, + active, + } => { + patch.react( + revision, + reaction, + location.map(|l| l.into()), + active, + &signer, + )?; + } + cobs::patch::Action::RevisionComment { + revision, + location, + body, + reply_to, + embeds, + } => { + patch.comment( + revision, + body, + reply_to, + location.map(|l| l.into()), + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::patch::Action::RevisionCommentEdit { + revision, + comment, + body, + embeds, + } => { + patch.comment_edit( + revision, + comment, + body, + embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + } + cobs::patch::Action::RevisionCommentReact { + revision, + comment, + reaction, + active, + } => { + patch.comment_react(revision, comment, reaction, active, &signer)?; + } + cobs::patch::Action::RevisionRedact { revision } => { + patch.redact(revision, &signer)?; + } + cobs::patch::Action::Merge { .. } => { + unimplemented!("We don't support merging of patches through the desktop") + } + cobs::patch::Action::Revision { .. } => { + unimplemented!("We don't support creating new revisions through the desktop") + } + } + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok::<_, Error>(cobs::patch::Patch::new(*patch.id(), &patch, &aliases)) + } + + /// Gets the draft review of the local user for a specific patch revision in a repository. + /// + /// This Tauri command is used to retrieve a patch review draft for the local user + /// on a given patch revision from a repository. + /// It looks up the repository using the provided repository ID (`rid`) and patch object ID (`cob_id`), + /// and gets the patch review of the local user associated with a specific revision (`revision_id`), if it exists. + fn get_draft_review( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + revision_id: patch::RevisionId, + ) -> Option { + let profile = self.profile(); + let repo = profile.storage.repository(rid).ok()?; + let signer = profile.signer().ok()?; + let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); + let patches = cob::patch::Cache::no_cache(&drafts).ok()?; + + let patch = patches.get(&cob_id.into()).ok()?; + let revision = patch.and_then(|p| p.revision(&revision_id).cloned()); + let review = revision.and_then(|rev| rev.review_by(signer.public_key()).cloned()); + + review + } + + /// Edits a draft review for a specific patch revision in a repository. + /// + /// This Tauri command allows users to edit a draft review for a specific patch review. + /// The draft is associated with the user (signer) and the provided patch revision within the repository. + fn edit_draft_review( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + edit: cobs::patch::ReviewEdit, + ) -> Result<(), Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); + + let mut patches = cob::patch::Cache::no_cache(&drafts)?; + let mut patch = patches.get_mut(&cob_id.into())?; + patch.review_edit( + edit.review_id, + edit.verdict, + edit.summary, + edit.labels, + &signer, + )?; + + patches.write(&cob_id.into())?; + + Ok::<_, Error>(()) + } + + /// Creates a draft review for a specific patch revision. + /// + /// This Tauri command allows users to create a new draft review for a specific patch revision. + /// The draft is associated with the user (signer) and the provided patch revision within the repository. + fn create_draft_review( + &self, + rid: identity::RepoId, + revision_id: cob::patch::RevisionId, + cob_id: git::Oid, + labels: Vec, + ) -> Result { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); + + let mut patches = cob::patch::Cache::no_cache(&drafts)?; + let mut patch = patches.get_mut(&cob_id.into())?; + let revision = patch + .revision(&revision_id) + .ok_or_else(|| Error::WithHint { + err: anyhow::anyhow!("patch revision not found"), + hint: "Not able to find the specified patch revision.", + })?; + + revision + .review_by(signer.public_key()) + .ok_or(Error::WithHint { + err: anyhow::anyhow!("duplicate patch review found"), + hint: "Found an existing draft patch review on this patch revision and repo.", + })?; + + let review_id = patch.review( + revision.id(), + Some(cob::patch::Verdict::Reject), + None, + labels, + &signer, + )?; + + patches.write(&cob_id.into())?; + + Ok::<_, Error>(review_id) + } + + /// Creates a new review comment on a draft review for a specific patch. + /// + /// This Tauri command is used to add a comment to an existing draft review in a repository. + /// It allows users to comment on a specific location in the code or leave general feedback + /// on a review that belongs to a specific patch. + fn create_draft_review_comment( + &self, + rid: identity::RepoId, + cob_id: git::Oid, + new: cobs::thread::CreateReviewComment, + ) -> Result<(), Error> { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let signer = profile.signer()?; + let drafts = storage::git::cob::DraftStore::new(&repo, *signer.public_key()); + + let mut patches = cob::patch::Cache::no_cache(&drafts)?; + let mut patch = patches.get_mut(&cob_id.into())?; + + patch.transaction("Review comments", &signer, |tx| { + tx.review_comment( + new.review_id, + new.body, + new.location.map(|l| l.into()), + new.reply_to, + new.embeds.into_iter().map(|e| e.into()).collect::>(), + )?; + + Ok(()) + })?; + + patches.write(&cob_id.into())?; + + Ok::<_, Error>(()) + } +} diff --git a/crates/radicle-types/src/traits/repo.rs b/crates/radicle-types/src/traits/repo.rs new file mode 100644 index 000000000..2439a2bcd --- /dev/null +++ b/crates/radicle-types/src/traits/repo.rs @@ -0,0 +1,142 @@ +use radicle::crypto::Verified; +use radicle::identity::doc::PayloadId; +use radicle::identity::DocAt; +use radicle::issue::cache::Issues as _; +use radicle::node::routing::Store; +use radicle::patch::cache::Patches as _; +use radicle::prelude::Doc; +use radicle::storage; +use radicle::storage::ReadRepository; +use radicle::storage::ReadStorage; +use radicle::{git, identity}; +use radicle_surf as surf; + +use crate::cobs; +use crate::cobs::diff::Options; +use crate::error::Error; +use crate::repo; +use crate::traits::Profile; + +pub trait Repo: Profile { + fn list_repos(&self) -> Result, Error> { + let profile = self.profile(); + let storage = &profile.storage; + let policies = profile.policies()?; + let mut repos = storage.repositories()?.into_iter().collect::>(); + repos.sort_by_key(|p| p.rid); + + let infos = repos + .into_iter() + .filter_map(|info| { + if !policies.is_seeding(&info.rid).unwrap_or_default() { + return None; + } + let repo = profile.storage.repository(info.rid).ok()?; + let DocAt { doc, .. } = repo.identity_doc().ok()?; + let repo_info = self.repo_info(&repo, &doc).ok()?; + + Some(repo_info) + }) + .collect::>(); + + Ok::<_, Error>(infos) + } + + fn repo_by_id(&self, rid: identity::RepoId) -> Result { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let DocAt { doc, .. } = repo.identity_doc()?; + + let repo_info = self.repo_info(&repo, &doc)?; + + Ok::<_, Error>(repo_info) + } + + fn diff_stats( + &self, + rid: identity::RepoId, + base: git::Oid, + head: git::Oid, + ) -> Result { + let profile = self.profile(); + let repo = radicle_surf::Repository::open(storage::git::paths::repository( + &profile.storage, + &rid, + ))?; + let base = repo.commit(base)?; + let commit = repo.commit(head)?; + let diff = repo.diff(base.id, commit.id)?; + let stats = diff.stats(); + + Ok::<_, Error>(cobs::Stats::new(stats)) + } + + fn repo_info( + &self, + repo: &storage::git::Repository, + doc: &Doc, + ) -> Result { + let profile = self.profile(); + let aliases = profile.aliases(); + let delegates = doc + .delegates + .clone() + .into_iter() + .map(|did| cobs::Author::new(did, &aliases)) + .collect::>(); + let db = profile.database()?; + let seeding = db.count(&repo.id).unwrap_or_default(); + let project = doc.payload.get(&PayloadId::project()).and_then(|payload| { + let (_, head) = repo.head().ok()?; + let commit = repo.commit(head).ok()?; + let patches = profile.patches(repo).ok()?; + let patches = patches.counts().ok()?; + let issues = profile.issues(repo).ok()?; + let issues = issues.counts().ok()?; + + let data: repo::ProjectPayloadData = (*payload).clone().try_into().ok()?; + let meta = repo::ProjectPayloadMeta { + issues, + patches, + head, + last_commit_timestamp: commit.time().seconds() * 1000, + }; + + Some(repo::ProjectPayload::new(data, meta)) + }); + + Ok::<_, Error>(repo::RepoInfo { + payloads: repo::SupportedPayloads { project }, + delegates, + threshold: doc.threshold, + visibility: doc.visibility.clone().into(), + rid: repo.id, + seeding, + }) + } + + fn get_diff(&self, rid: identity::RepoId, options: Options) -> Result { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?.backend; + let base = repo.find_commit(*options.base)?; + let head = repo.find_commit(*options.head)?; + + let mut opts = git::raw::DiffOptions::new(); + opts.patience(true) + .minimal(true) + .context_lines(options.unified); + + let mut find_opts = git::raw::DiffFindOptions::new(); + find_opts.exact_match_only(true); + find_opts.all(true); + + let left = base.tree()?; + let right = head.tree()?; + + let mut diff = repo.diff_tree_to_tree(Some(&left), Some(&right), Some(&mut opts))?; + diff.find_similar(Some(&mut find_opts))?; + let diff = surf::diff::Diff::try_from(diff)?; + + Ok::<_, Error>(diff) + } +} diff --git a/crates/radicle-types/src/traits/thread.rs b/crates/radicle-types/src/traits/thread.rs new file mode 100644 index 000000000..a866b58ec --- /dev/null +++ b/crates/radicle-types/src/traits/thread.rs @@ -0,0 +1,121 @@ +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use localtime::LocalTime; + +use radicle::cob; +use radicle::git; +use radicle::identity; +use radicle::node::Handle; +use radicle::storage::ReadRepository; +use radicle::storage::ReadStorage; +use radicle::Node; + +use crate::cobs; +use crate::error::Error; +use crate::traits::Profile; + +pub trait Thread: Profile { + fn get_embed(&self, rid: identity::RepoId, oid: git::Oid) -> Result { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let blob = repo.blob(oid)?; + + Ok::<_, Error>(STANDARD.encode(blob.content())) + } + + fn save_embed( + &self, + rid: identity::RepoId, + name: &str, + bytes: &[u8], + ) -> Result { + let profile = self.profile(); + let repo = profile.storage.repository(rid)?; + let embed = radicle::cob::Embed::::store(name, bytes, &repo.backend)?; + + Ok(embed.oid()) + } + + fn create_issue_comment( + &self, + rid: identity::RepoId, + new: cobs::thread::NewIssueComment, + opts: cobs::CobOptions, + ) -> Result, Error> { + let profile = self.profile(); + let aliases = &profile.aliases(); + let mut node = Node::new(profile.socket()); + let signer = profile.signer()?; + let repo = profile.storage.repository(rid)?; + let mut issues = profile.issues_mut(&repo)?; + let mut issue = issues.get_mut(&new.id.into())?; + let id = new.reply_to.unwrap_or_else(|| { + let (root_id, _) = issue.root(); + *root_id + }); + let n = new.clone(); + let oid = issue.comment( + n.body, + id, + n.embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok(cobs::thread::Comment::::new( + oid, + cob::thread::Comment::new( + *signer.public_key(), + new.body, + id.into(), + None, + new.embeds.into_iter().map(|e| e.into()).collect::>(), + LocalTime::now().into(), + ), + aliases, + )) + } + + fn create_patch_comment( + &self, + rid: identity::RepoId, + new: cobs::thread::NewPatchComment, + opts: cobs::CobOptions, + ) -> Result, Error> { + let profile = self.profile(); + let aliases = &profile.aliases(); + let mut node = Node::new(profile.socket()); + let signer = profile.signer()?; + let repo = profile.storage.repository(rid)?; + let mut patches = profile.patches_mut(&repo)?; + let mut patch = patches.get_mut(&new.id.into())?; + let n = new.clone(); + let oid = patch.comment( + new.revision.into(), + n.body, + n.reply_to, + n.location.map(|l| l.into()), + n.embeds.into_iter().map(|e| e.into()).collect::>(), + &signer, + )?; + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok(cobs::thread::Comment::::new( + oid, + cob::thread::Comment::new( + *signer.public_key(), + new.body, + new.reply_to, + new.location.map(|l| l.into()), + new.embeds.into_iter().map(|e| e.into()).collect::>(), + LocalTime::now().into(), + ), + aliases, + )) + } +} diff --git a/crates/test-http-api/Cargo.lock b/crates/test-http-api/Cargo.lock new file mode 100644 index 000000000..d8e32d8d1 --- /dev/null +++ b/crates/test-http-api/Cargo.lock @@ -0,0 +1,2810 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "amplify" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7147b742325842988dd6c793d55f58df3ae36bccf7d9b6e07db10ab035be343d" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "wasm-bindgen", +] + +[[package]] +name = "amplify_derive" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6309e6b8d89b36b9f959b7a8fa093583b94922a0f6438a24fb08936de4d428" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify_num" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99bcb75a2982047f733547042fc3968c0f460dfcf7d90b90dea3b2744580e9ad" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "async-trait" +version = "0.1.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ct-codecs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "026ac6ceace6298d2c557ef5ed798894962296469ec7842288ea64674201a2d1" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cypheraddr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5c54d2ad4ab9941383519471b75d12abc1a7b4779265e233168f2703a730d9" +dependencies = [ + "amplify", + "base32", + "cyphergraphy", + "sha3", +] + +[[package]] +name = "cyphergraphy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67c16c8ef5ddcdab57aab83fd8e770540ea3682ccdae09642c63575b0da2184" +dependencies = [ + "amplify", + "ec25519", +] + +[[package]] +name = "cyphernet" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac949369884a7a1d802cc669821269c707be8cec4d65043382e253733d2e62e1" +dependencies = [ + "cypheraddr", + "cyphergraphy", + "socks5-client", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-encoding-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ec25519" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfd533a2fc01178c738c99412ae1f7e1ad2cb37c2e14bfd87e9d4618171c825" +dependencies = [ + "ct-codecs", + "ed25519", + "getrandom", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox 0.1.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "git-ref-format" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1" +dependencies = [ + "git-ref-format-core", + "git-ref-format-macro", +] + +[[package]] +name = "git-ref-format-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424" +dependencies = [ + "serde", + "thiserror", +] + +[[package]] +name = "git-ref-format-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4" +dependencies = [ + "git-ref-format-core", + "proc-macro-error", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags 2.6.0", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.6.0", + "dyn-clone", + "fxhash", + "newline-converter", + "once_cell", + "tempfile", + "termion 2.0.3", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lexopt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.3", +] + +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "localtime" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016a009e0bb8ba6e3229fb74bf11a8fe6ef24542cc6ef35ef38863ac13f96d87" +dependencies = [ + "serde", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "nonempty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995defdca0a589acfdd1bd2e8e3b896b4d4f7675a31fd14c32611440c7f608e6" +dependencies = [ + "serde", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qcheck" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b439bd4242da51d62d18c95e6a6add749346756b0d1a587dfd0cc22fa6b5f3f0" +dependencies = [ + "rand", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radicle" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a818569c11f1bac56f38b002d778ce8ec92e312024b9aebcd68bad5dee6a465" +dependencies = [ + "amplify", + "base64 0.21.7", + "crossbeam-channel", + "cyphernet", + "fastrand", + "git2", + "libc", + "localtime", + "log", + "multibase", + "nonempty", + "once_cell", + "radicle-cob", + "radicle-crypto", + "radicle-git-ext", + "radicle-ssh", + "serde", + "serde_json", + "siphasher", + "sqlite", + "tempfile", + "thiserror", + "unicode-normalization", +] + +[[package]] +name = "radicle-cob" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fac94999d8ffb6e88674bee487b080b69bbc9fb1b439ebfa51481ede1a17b3" +dependencies = [ + "fastrand", + "git2", + "log", + "nonempty", + "once_cell", + "radicle-crypto", + "radicle-dag", + "radicle-git-ext", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "radicle-crypto" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d6a67969719841ad06049597006368eb4238ca63a02d20207654dfd1d2d6ad" +dependencies = [ + "amplify", + "cyphernet", + "ec25519", + "fastrand", + "multibase", + "qcheck", + "radicle-git-ext", + "radicle-ssh", + "serde", + "sqlite", + "ssh-key", + "thiserror", + "zeroize", +] + +[[package]] +name = "radicle-dag" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a678c3049a88ae6a34dd9f52ea9a5f9f066a0af63466b75cf8c48840303067" +dependencies = [ + "fastrand", +] + +[[package]] +name = "radicle-git-ext" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650" +dependencies = [ + "git-ref-format", + "git2", + "percent-encoding", + "radicle-std-ext", + "serde", + "thiserror", +] + +[[package]] +name = "radicle-httpd" +version = "0.16.0" +dependencies = [ + "anyhow", + "axum", + "base64 0.22.1", + "chrono", + "flate2", + "hyper", + "lexopt", + "lru", + "nonempty", + "pretty_assertions", + "radicle", + "radicle-crypto", + "radicle-surf", + "radicle-term", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "tower 0.5.0", + "tower-http", + "tracing", + "tracing-logfmt", + "tracing-subscriber", +] + +[[package]] +name = "radicle-signals" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fba65f6ed964e6e8d34d935f83d40c506f14bf45e60c635042ca4ad4185f149" +dependencies = [ + "crossbeam-channel", + "libc", +] + +[[package]] +name = "radicle-ssh" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee758010fb64482be4b18591fbeb3cbc15b16450d143edf4edb5484c7366c6" +dependencies = [ + "byteorder", + "log", + "thiserror", + "zeroize", +] + +[[package]] +name = "radicle-std-ext" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db20136bbc9ae63f3fec8e5a6c369f4902fac2244501b5dfc6d668e43475aaa4" + +[[package]] +name = "radicle-surf" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf6aff57520e8e7200bf7826ddc5ccf8d4612dd880497a4f256c3d272eeb805" +dependencies = [ + "anyhow", + "base64 0.21.7", + "flate2", + "git2", + "log", + "nonempty", + "radicle-git-ext", + "radicle-std-ext", + "serde", + "tar", + "thiserror", + "url", +] + +[[package]] +name = "radicle-term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cf3256980e3fddcd135f6e755022df8b385b842cdcbbfce059f47e87caec18" +dependencies = [ + "anstyle-query", + "anyhow", + "crossbeam-channel", + "inquire", + "libc", + "once_cell", + "radicle-signals", + "shlex", + "termion 3.0.0", + "thiserror", + "unicode-display-width", + "unicode-segmentation", + "zeroize", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature 2.2.0", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socks5-client" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc7dcf6fab1d65d82d633006a4cc658d76ce436e01cf1a7c71873c0eeba324c" +dependencies = [ + "amplify", + "cypheraddr", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03801c10193857d6a4a71ec46cee198a15cbc659622aabe1db0d0bdbefbcf8e6" +dependencies = [ + "libc", + "sqlite3-sys", +] + +[[package]] +name = "sqlite3-src" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc95a51a1ee38839599371685b9d4a926abb51791f0bc3bf8c3bb7867e6e454" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "sqlite3-sys" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2752c669433e40ebb08fde824146f50d9628aa0b66a3b7fc6be34db82a8063b" +dependencies = [ + "libc", + "sqlite3-src", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "poly1305", + "ssh-encoding", + "subtle", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +dependencies = [ + "bcrypt-pbkdf", + "p256", + "p384", + "p521", + "rand_core", + "rsa", + "sec1", + "sha2", + "signature 2.2.0", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termion" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d" +dependencies = [ + "libc", + "libredox 0.0.2", + "numtoa", + "redox_termios", +] + +[[package]] +name = "termion" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417813675a504dfbbf21bfde32c03e5bf9f2413999962b479023c02848c1c7a5" +dependencies = [ + "libc", + "libredox 0.0.2", + "numtoa", + "redox_termios", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b837f86b25d7c0d7988f00a54e74739be6477f2aac6201b8f429a7569991b7" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-logfmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1f47d22deb79c3f59fcf2a1f00f60cbdc05462bf17d1cd356c1fefa3f444bd" +dependencies = [ + "time", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-display-width" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a43273b656140aa2bb8e65351fe87c255f0eca706b2538a9bd4a590a3490bf3" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/crates/test-http-api/Cargo.toml b/crates/test-http-api/Cargo.toml new file mode 100644 index 000000000..f8dd07c49 --- /dev/null +++ b/crates/test-http-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "test-http-api" +description = "HTTP Test API" +homepage = "https://radicle.xyz" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = { version = "1.0.90" } +axum = { version = "0.7.5", default-features = false, features = ["json", "query", "tokio", "http1"] } +hyper = { version = "1.4", default-features = false } +lexopt = { version = "0.3.0" } +radicle = { git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git" } +radicle-surf = { version = "0.22.1", default-features = false, features = ["serde"] } +radicle-types = { path = "../radicle-types" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } +thiserror = { version = "1" } +tokio = { version = "1.40", default-features = false, features = ["macros", "rt-multi-thread"] } +tower-http = { version = "0.5.2", default-features = false, features = ["cors", "set-header"] } diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs new file mode 100644 index 000000000..bbbf6127e --- /dev/null +++ b/crates/test-http-api/src/api.rs @@ -0,0 +1,306 @@ +use std::ops::Deref; +use std::sync::Arc; + +use axum::extract::State; +use axum::response::{IntoResponse, Json}; +use axum::routing::post; +use axum::Router; +use hyper::header::CONTENT_TYPE; +use hyper::Method; +use radicle::cob::TypeName; +use serde::{Deserialize, Serialize}; +use tower_http::cors::{self, CorsLayer}; + +use radicle::{git, identity}; +use radicle_types as types; +use radicle_types::cobs::issue::{Action, NewIssue}; +use radicle_types::cobs::CobOptions; +use radicle_types::traits::auth::Auth; +use radicle_types::traits::cobs::Cobs; +use radicle_types::traits::issue::{Issues, IssuesMut}; +use radicle_types::traits::patch::{Patches, PatchesMut}; +use radicle_types::traits::repo::Repo; +use radicle_types::traits::thread::Thread; +use radicle_types::traits::Profile; + +use crate::error::Error; + +#[derive(Clone)] +pub struct Context { + profile: Arc, +} + +impl Auth for Context {} +impl Repo for Context {} +impl Cobs for Context {} +impl Thread for Context {} +impl Issues for Context {} +impl IssuesMut for Context {} +impl Patches for Context {} +impl PatchesMut for Context {} +impl Profile for Context { + fn profile(&self) -> radicle::Profile { + self.profile.deref().clone() + } +} + +impl Context { + pub fn new(profile: Arc) -> Self { + Self { profile } + } +} + +pub fn router(ctx: Context) -> Router { + Router::new() + .route("/config", post(config_handler)) + .route("/authenticate", post(auth_handler)) + .route("/list_repos", post(repo_root_handler)) + .route("/repo_by_id", post(repo_handler)) + .route("/diff_stats", post(diff_stats_handler)) + .route("/activity_by_id", post(activity_handler)) + .route("/get_diff", post(diff_handler)) + .route("/list_issues", post(issues_handler)) + .route("/create_issue", post(create_issue_handler)) + .route("/edit_issue", post(edit_issue_handler)) + .route("/issue_by_id", post(issue_handler)) + .route("/list_patches", post(patches_handler)) + .route("/patch_by_id", post(patch_handler)) + .route("/revisions_by_patch", post(revision_handler)) + .route("/get_file_by_oid", post(get_embeds_handler)) + .route("/save_embed", post(save_embed_handler)) + .layer( + CorsLayer::new() + .allow_origin(cors::Any) + .allow_methods([Method::POST, Method::GET]) + .allow_headers([CONTENT_TYPE]), + ) + .with_state(ctx) +} + +async fn config_handler(State(ctx): State) -> impl IntoResponse { + let config = ctx.config(); + + Ok::<_, Error>(Json(config)) +} + +async fn auth_handler(State(ctx): State) -> impl IntoResponse { + ctx.authenticate()?; + + Ok::<_, Error>(Json(())) +} + +async fn repo_root_handler(State(ctx): State) -> impl IntoResponse { + let repos = ctx.list_repos()?; + + Ok::<_, Error>(Json(repos)) +} + +#[derive(Serialize, Deserialize)] +struct RepoBody { + pub rid: identity::RepoId, +} + +async fn repo_handler( + State(ctx): State, + Json(RepoBody { rid }): Json, +) -> impl IntoResponse { + let info = ctx.repo_by_id(rid)?; + + Ok::<_, Error>(Json(info)) +} + +#[derive(Serialize, Deserialize)] +struct DiffStatsBody { + pub rid: identity::RepoId, + pub base: git::Oid, + pub head: git::Oid, +} + +async fn diff_stats_handler( + State(ctx): State, + Json(DiffStatsBody { rid, base, head }): Json, +) -> impl IntoResponse { + let info = ctx.diff_stats(rid, base, head)?; + + Ok::<_, Error>(Json(info)) +} + +#[derive(Serialize, Deserialize)] +struct DiffBody { + pub rid: identity::RepoId, + pub options: types::cobs::diff::Options, +} + +async fn diff_handler( + State(ctx): State, + Json(DiffBody { rid, options }): Json, +) -> impl IntoResponse { + let info = ctx.get_diff(rid, options)?; + + Ok::<_, Error>(Json(info)) +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct IssuesBody { + pub rid: identity::RepoId, + pub status: Option, +} + +async fn issues_handler( + State(ctx): State, + Json(IssuesBody { rid, status }): Json, +) -> impl IntoResponse { + let issues = ctx.list_issues(rid, status)?; + + Ok::<_, Error>(Json(issues)) +} + +#[derive(Serialize, Deserialize)] +struct CreateIssuesBody { + pub rid: identity::RepoId, + pub opts: CobOptions, + pub new: NewIssue, +} + +async fn create_issue_handler( + State(ctx): State, + Json(CreateIssuesBody { rid, opts, new }): Json, +) -> impl IntoResponse { + let issues = ctx.create_issue(rid, new, opts)?; + + Ok::<_, Error>(Json(issues)) +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct EditIssuesBody { + pub rid: identity::RepoId, + pub cob_id: git::Oid, + pub action: Action, + pub opts: CobOptions, +} + +async fn edit_issue_handler( + State(ctx): State, + Json(EditIssuesBody { + rid, + cob_id, + action, + opts, + }): Json, +) -> impl IntoResponse { + let issues = ctx.edit_issue(rid, cob_id, action, opts)?; + + Ok::<_, Error>(Json(issues)) +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ActivityBody { + pub rid: identity::RepoId, + pub type_name: TypeName, + pub id: git::Oid, +} + +async fn activity_handler( + State(ctx): State, + Json(ActivityBody { rid, type_name, id }): Json, +) -> impl IntoResponse { + let activity = ctx.activity_by_id(rid, type_name, id)?; + + Ok::<_, Error>(Json(activity)) +} + +#[derive(Serialize, Deserialize)] +struct EmbedBody { + pub rid: identity::RepoId, + pub oid: git::Oid, +} + +async fn get_embeds_handler( + State(ctx): State, + Json(EmbedBody { rid, oid }): Json, +) -> impl IntoResponse { + let embed = ctx.get_embed(rid, oid)?; + + Ok::<_, Error>(Json(embed)) +} + +#[derive(Serialize, Deserialize)] +struct CreateEmbedBody { + pub rid: identity::RepoId, + pub name: String, + pub content: Vec, +} + +async fn save_embed_handler( + State(ctx): State, + Json(CreateEmbedBody { rid, name, content }): Json, +) -> impl IntoResponse { + let embed = ctx.save_embed(rid, &name, &content)?; + + Ok::<_, Error>(Json(embed)) +} + +#[derive(Serialize, Deserialize)] +struct IssueBody { + pub rid: identity::RepoId, + pub id: git::Oid, +} + +async fn issue_handler( + State(ctx): State, + Json(IssueBody { rid, id }): Json, +) -> impl IntoResponse { + let issue = ctx.issue_by_id(rid, id)?; + + Ok::<_, Error>(Json(issue)) +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PatchesBody { + pub rid: identity::RepoId, + pub page: Option, + pub per_page: Option, + pub status: Option, +} + +async fn patches_handler( + State(ctx): State, + Json(PatchesBody { + rid, + page, + per_page, + status, + }): Json, +) -> impl IntoResponse { + let patches = ctx.list_patches(rid, status, page, per_page)?; + + Ok::<_, Error>(Json(patches)) +} + +#[derive(Serialize, Deserialize)] +struct PatchBody { + pub rid: identity::RepoId, + pub id: git::Oid, +} + +async fn patch_handler( + State(ctx): State, + Json(PatchBody { rid, id }): Json, +) -> impl IntoResponse { + let patch = ctx.get_patch(rid, id)?; + + Ok::<_, Error>(Json(patch)) +} + +async fn revision_handler( + State(ctx): State, + Json(PatchBody { rid, id }): Json, +) -> impl IntoResponse { + let revisions = ctx.revisions_by_patch(rid, id)?; + + Ok::<_, Error>(Json(revisions)) +} diff --git a/crates/test-http-api/src/error.rs b/crates/test-http-api/src/error.rs new file mode 100644 index 000000000..5cc9103b8 --- /dev/null +++ b/crates/test-http-api/src/error.rs @@ -0,0 +1,32 @@ +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::Json; +use serde_json::json; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// radicle_types error. + #[error(transparent)] + Types(#[from] radicle_types::error::Error), +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + let (status, msg) = match self { + other => { + if cfg!(debug_assertions) { + (StatusCode::INTERNAL_SERVER_ERROR, Some(other.to_string())) + } else { + (StatusCode::INTERNAL_SERVER_ERROR, None) + } + } + }; + + let body = Json(json!({ + "error": msg.or_else(|| status.canonical_reason().map(|r| r.to_string())), + "code": status.as_u16() + })); + + (status, body).into_response() + } +} diff --git a/crates/test-http-api/src/lib.rs b/crates/test-http-api/src/lib.rs new file mode 100644 index 000000000..9d66e0a1c --- /dev/null +++ b/crates/test-http-api/src/lib.rs @@ -0,0 +1,32 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use axum::Router; +use tokio::net::TcpListener; + +use radicle::Profile; + +mod api; +mod error; + +#[derive(Debug, Clone)] +pub struct Options { + pub listen: SocketAddr, +} + +pub async fn run(options: Options) -> anyhow::Result<()> { + let profile = Profile::load()?; + let listener = TcpListener::bind(options.listen).await?; + let app = router(profile)?.into_make_service_with_connect_info::(); + + axum::serve(listener, app) + .await + .map_err(anyhow::Error::from) +} + +fn router(profile: Profile) -> anyhow::Result { + let profile = Arc::new(profile); + let ctx = api::Context::new(profile); + + Ok(api::router(ctx)) +} diff --git a/crates/test-http-api/src/main.rs b/crates/test-http-api/src/main.rs new file mode 100644 index 000000000..4560d3f13 --- /dev/null +++ b/crates/test-http-api/src/main.rs @@ -0,0 +1,35 @@ +use std::process; + +use test_http_api as api; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let options = parse_options()?; + match api::run(options).await { + Ok(()) => {} + Err(_) => { + process::exit(1); + } + } + Ok(()) +} + +fn parse_options() -> Result { + use lexopt::prelude::*; + + let mut parser = lexopt::Parser::from_env(); + let mut listen = None; + + while let Some(arg) = parser.next()? { + match arg { + Long("listen") => { + let addr = parser.value()?.parse()?; + listen = Some(addr); + } + _ => return Err(arg.unexpected()), + } + } + Ok(api::Options { + listen: listen.unwrap_or_else(|| ([0, 0, 0, 0], 8080).into()), + }) +} diff --git a/package.json b/package.json index 731f2b78f..9ea83e55d 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "type": "module", "scripts": { "start": "vite", + "start:http": "cargo run --manifest-path ./crates/test-http-api/Cargo.toml", "build": "vite build && scripts/copy-katex-assets && scripts/install-twemoji-assets", "postinstall": "scripts/copy-katex-assets && scripts/install-twemoji-assets", "preview": "vite preview", diff --git a/src/App.svelte b/src/App.svelte index d3e1e4cf3..aa755b043 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -3,7 +3,7 @@ import { onDestroy, onMount } from "svelte"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import { listen } from "@tauri-apps/api/event"; import * as router from "@app/lib/router"; @@ -25,13 +25,15 @@ let unlistenNodeEvents: UnlistenFn | undefined = undefined; onMount(async () => { - unlistenEvents = await listen("event", event => { - console.log(event.payload); - }); + if (window.__TAURI_INTERNALS__) { + unlistenEvents = await listen("event", event => { + console.log(event.payload); + }); - unlistenNodeEvents = await listen("node_running", event => { - nodeRunning.set(event.payload); - }); + unlistenNodeEvents = await listen("node_running", event => { + nodeRunning.set(event.payload); + }); + } try { await invoke("authenticate"); diff --git a/src/components/Markdown.svelte b/src/components/Markdown.svelte index a68aa9c7d..2e52c39e2 100644 --- a/src/components/Markdown.svelte +++ b/src/components/Markdown.svelte @@ -7,7 +7,7 @@ import { Renderer, markdownWithExtensions } from "@app/lib/markdown"; import { highlight } from "@app/lib/syntax"; import { twemoji, scrollIntoView, isCommit } from "@app/lib/utils"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; export let rid: string; export let content: string; diff --git a/src/components/PatchTeaser.svelte b/src/components/PatchTeaser.svelte index 4450bebcb..8537a7ebb 100644 --- a/src/components/PatchTeaser.svelte +++ b/src/components/PatchTeaser.svelte @@ -8,7 +8,7 @@ patchStatusBackgroundColor, patchStatusColor, } from "@app/lib/utils"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import { onMount } from "svelte"; import { push } from "@app/lib/router"; diff --git a/src/lib/invoke.ts b/src/lib/invoke.ts new file mode 100644 index 000000000..c99c45431 --- /dev/null +++ b/src/lib/invoke.ts @@ -0,0 +1,21 @@ +import * as tauri from "@tauri-apps/api/core"; + +export async function invoke( + cmd: string, + args?: tauri.InvokeArgs, + options?: tauri.InvokeOptions, +): Promise { + if (window.__TAURI_INTERNALS__) { + return tauri.invoke(cmd, args, options); + } else { + return fetch(`http://127.0.0.1:8080/${cmd}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(args), + }) + .then(data => data.json()) + .catch(() => { + throw Error(`Issue with HTTP Route: ${JSON.stringify({ cmd, args })}`); + }); + } +} diff --git a/src/lib/router/definitions.ts b/src/lib/router/definitions.ts index 8897254a0..f9517e94e 100644 --- a/src/lib/router/definitions.ts +++ b/src/lib/router/definitions.ts @@ -2,7 +2,7 @@ import type { Config } from "@bindings/config/Config"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; import type { LoadedRepoRoute, RepoRoute } from "@app/views/repo/router"; -import { invoke } from "@tauri-apps/api/core"; +import { invoke } from "@app/lib/invoke"; import { loadCreateIssue, diff --git a/src/views/repo/CreateIssue.svelte b/src/views/repo/CreateIssue.svelte index 118632294..0fae3faa9 100644 --- a/src/views/repo/CreateIssue.svelte +++ b/src/views/repo/CreateIssue.svelte @@ -4,7 +4,7 @@ import type { Issue } from "@bindings/cob/issue/Issue"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import { issueStatusColor } from "@app/lib/utils"; import * as router from "@app/lib/router"; diff --git a/src/views/repo/Issue.svelte b/src/views/repo/Issue.svelte index c2ecfe077..51c5ddb55 100644 --- a/src/views/repo/Issue.svelte +++ b/src/views/repo/Issue.svelte @@ -14,7 +14,7 @@ publicKeyFromDid, scrollIntoView, } from "@app/lib/utils"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import { announce } from "@app/components/AnnounceSwitch.svelte"; diff --git a/src/views/repo/Patch.svelte b/src/views/repo/Patch.svelte index 72c8a6626..258e19b27 100644 --- a/src/views/repo/Patch.svelte +++ b/src/views/repo/Patch.svelte @@ -9,7 +9,7 @@ formatTimestamp, patchStatusColor, } from "@app/lib/utils"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import Border from "@app/components/Border.svelte"; import CopyableId from "@app/components/CopyableId.svelte"; diff --git a/src/views/repo/Patches.svelte b/src/views/repo/Patches.svelte index 104d97b01..3dfdc6d16 100644 --- a/src/views/repo/Patches.svelte +++ b/src/views/repo/Patches.svelte @@ -5,7 +5,7 @@ import type { PatchStatus } from "./router"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; - import { invoke } from "@tauri-apps/api/core"; + import { invoke } from "@app/lib/invoke"; import Layout from "./Layout.svelte"; import Border from "@app/components/Border.svelte"; diff --git a/src/views/repo/router.ts b/src/views/repo/router.ts index 9849e316d..d9858a868 100644 --- a/src/views/repo/router.ts +++ b/src/views/repo/router.ts @@ -5,7 +5,7 @@ import type { Patch } from "@bindings/cob/patch/Patch"; import type { RepoInfo } from "@bindings/repo/RepoInfo"; import type { Revision } from "@bindings/cob/patch/Revision"; -import { invoke } from "@tauri-apps/api/core"; +import { invoke } from "@app/lib/invoke"; import { unreachable } from "@app/lib/utils"; export type IssueStatus = "all" | Issue["state"]["status"];