diff --git a/src-tauri/bindings/CobOptions.ts b/src-tauri/bindings/CobOptions.ts new file mode 100644 index 000000000..54405f609 --- /dev/null +++ b/src-tauri/bindings/CobOptions.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CobOptions = { announce?: boolean }; diff --git a/src-tauri/bindings/CodeLocation.ts b/src-tauri/bindings/CodeLocation.ts new file mode 100644 index 000000000..8a7837226 --- /dev/null +++ b/src-tauri/bindings/CodeLocation.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodeRange } from "./CodeRange"; + +export type CodeLocation = { + commit: string; + path: string; + old: CodeRange | null; + new: CodeRange | null; +}; diff --git a/src-tauri/bindings/CodeRange.ts b/src-tauri/bindings/CodeRange.ts new file mode 100644 index 000000000..96d19d3c5 --- /dev/null +++ b/src-tauri/bindings/CodeRange.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CodeRange = + | { type: "lines"; range: { start: number; end: number } } + | { type: "chars"; line: number; range: { start: number; end: number } }; diff --git a/src-tauri/bindings/NewIssueComment.ts b/src-tauri/bindings/NewIssueComment.ts new file mode 100644 index 000000000..ae9dcba6a --- /dev/null +++ b/src-tauri/bindings/NewIssueComment.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NewIssueComment = { + id: string; + body: string; + replyTo?: string; + embeds: { name: string; content: string }[]; +}; diff --git a/src-tauri/bindings/NewPatchComment.ts b/src-tauri/bindings/NewPatchComment.ts new file mode 100644 index 000000000..420cf2d1d --- /dev/null +++ b/src-tauri/bindings/NewPatchComment.ts @@ -0,0 +1,11 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodeLocation } from "./CodeLocation"; + +export type NewPatchComment = { + id: string; + revision: string; + body: string; + replyTo?: string; + location: CodeLocation | null; + embeds: { name: string; content: string }[]; +}; diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 0198bbe1c..ad1079fc1 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -2,3 +2,4 @@ pub mod auth; pub mod cobs; pub mod profile; pub mod repos; +pub mod thread; diff --git a/src-tauri/src/commands/thread.rs b/src-tauri/src/commands/thread.rs new file mode 100644 index 000000000..2910e63c8 --- /dev/null +++ b/src-tauri/src/commands/thread.rs @@ -0,0 +1,68 @@ +use std::str::FromStr; + +use radicle::cob::{EntryId, ObjectId}; +use radicle::git::Oid; +use radicle::identity::RepoId; +use radicle::node::Handle; +use radicle::storage::ReadStorage; +use radicle::Node; + +use crate::error::Error; +use crate::types::cobs::{CobOptions, NewIssueComment, NewPatchComment}; +use crate::AppState; + +#[tauri::command] +pub fn create_issue_comment( + ctx: tauri::State, + rid: RepoId, + new: NewIssueComment, + opts: CobOptions, +) -> Result { + let mut node = Node::new(ctx.profile.socket()); + let signer = ctx.profile.signer()?; + let issue_id = ObjectId::from_str(&new.id)?; + let repo = ctx.profile.storage.repository(rid)?; + let mut issues = ctx.profile.issues_mut(&repo)?; + let mut issue = issues.get_mut(&issue_id)?; + let id = new.reply_to.unwrap_or_else(|| { + let (root_id, _) = issue.root(); + *root_id + }); + let oid = issue.comment(new.body, id, new.embeds, &signer)?; + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok::<_, Error>(oid) +} + +#[tauri::command] +pub fn create_patch_comment( + ctx: tauri::State, + rid: RepoId, + new: NewPatchComment, + opts: CobOptions, +) -> Result { + let mut node = Node::new(ctx.profile.socket()); + let signer = ctx.profile.signer()?; + let patch_id = ObjectId::from_str(&new.id)?; + let revision_id = EntryId::from_str(&new.revision)?; + let repo = ctx.profile.storage.repository(rid)?; + let mut patches = ctx.profile.patches_mut(&repo)?; + let mut patch = patches.get_mut(&patch_id)?; + let oid = patch.comment( + revision_id.into(), + new.body, + new.reply_to, + new.location.map(|l| l.into()), + new.embeds, + &signer, + )?; + + if opts.announce() { + node.announce_refs(rid)?; + } + + Ok::<_, Error>(oid) +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index 664f03b4b..c0fc38805 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -54,6 +54,14 @@ pub enum Error { #[error(transparent)] Surf(#[from] radicle_surf::Error), + /// Issue error. + #[error(transparent)] + Issue(#[from] radicle::issue::Error), + + /// Patch error. + #[error(transparent)] + Patch(#[from] radicle::patch::Error), + /// Crypto error. #[error(transparent)] Crypto(#[from] radicle::crypto::ssh::keystore::Error), @@ -77,6 +85,10 @@ pub enum Error { #[error(transparent)] Tauri(#[from] tauri::Error), + /// Parse ObjectId error. + #[error(transparent)] + ParseObjectId(#[from] radicle::cob::object::ParseObjectId), + /// Storage error. #[error(transparent)] Storage(#[from] radicle::storage::Error), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 055102639..f70343eb7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,7 +17,7 @@ use radicle::storage::git::Repository; use radicle::storage::{ReadRepository, ReadStorage}; use radicle::Node; -use commands::{auth, cobs, profile, repos}; +use commands::{auth, cobs, profile, repos, thread}; use types::repo::SupportedPayloads; struct AppState { @@ -142,6 +142,8 @@ pub fn run() { repos::repo_by_id, cobs::list_issues, cobs::list_patches, + thread::create_issue_comment, + thread::create_patch_comment, profile::config, ]) .run(tauri::generate_context!()) diff --git a/src-tauri/src/types/cobs.rs b/src-tauri/src/types/cobs.rs index 77ec61903..1f8c56f8d 100644 --- a/src-tauri/src/types/cobs.rs +++ b/src-tauri/src/types/cobs.rs @@ -1,8 +1,10 @@ use radicle::cob; +use radicle::git; use radicle::identity; use radicle::issue; use radicle::node::{Alias, AliasStore}; use radicle::patch; +use serde::Deserialize; use serde::Serialize; use ts_rs::TS; @@ -103,3 +105,112 @@ impl Patch { } } } + +#[derive(TS, Serialize, Deserialize)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct NewPatchComment { + pub id: String, + pub revision: String, + pub body: String, + #[ts(as = "Option")] + #[ts(optional)] + pub reply_to: Option, + pub location: Option, + #[ts(type = "{ name: string, content: string }[]")] + pub embeds: Vec, +} + +#[derive(TS, Serialize, Deserialize)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct NewIssueComment { + pub id: String, + pub body: String, + #[ts(as = "Option")] + #[ts(optional)] + pub reply_to: Option, + #[ts(type = "{ name: string, content: string }[]")] + pub embeds: Vec, +} + +#[derive(TS, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct CodeLocation { + #[ts(as = "String")] + commit: git::Oid, + path: std::path::PathBuf, + old: Option, + new: Option, +} + +impl From for CodeLocation { + fn from(val: cob::CodeLocation) -> Self { + Self { + commit: val.commit, + path: val.path, + old: val.old.map(|o| o.into()), + new: val.new.map(|o| o.into()), + } + } +} + +impl From for cob::CodeLocation { + fn from(val: CodeLocation) -> Self { + Self { + commit: val.commit, + path: val.path, + old: val.old.map(|o| o.into()), + new: val.new.map(|o| o.into()), + } + } +} + +#[derive(TS, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type")] +#[ts(export)] +pub enum CodeRange { + Lines { + #[ts(type = "{ start: number, end: number }")] + range: std::ops::Range, + }, + Chars { + line: usize, + #[ts(type = "{ start: number, end: number }")] + range: std::ops::Range, + }, +} + +impl From for CodeRange { + fn from(val: cob::CodeRange) -> Self { + match val { + cob::CodeRange::Chars { line, range } => Self::Chars { line, range }, + cob::CodeRange::Lines { range } => Self::Lines { range }, + } + } +} + +impl From for cob::CodeRange { + fn from(val: CodeRange) -> Self { + match val { + CodeRange::Chars { line, range } => Self::Chars { line, range }, + CodeRange::Lines { range } => Self::Lines { range }, + } + } +} + +#[derive(TS, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct CobOptions { + #[ts(as = "Option")] + #[ts(optional)] + announce: Option, +} + +impl CobOptions { + pub fn announce(&self) -> bool { + self.announce.unwrap_or(true) + } +}