radicle-cli: use Did across CLI

The CLI commands and output use PublicKey, ActorId, and NodeId for
peer identifiers.

Instead Did should be standardised for use in CLI arguments and
outputs.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-02-16 12:40:15 +00:00 committed by Alexis Sellier
parent b57a4606f6
commit 5477a3615f
No known key found for this signature in database
15 changed files with 94 additions and 103 deletions

View File

@ -7,7 +7,7 @@ $ rad auth
Initializing your 🌱 profile and identity Initializing your 🌱 profile and identity
ok Creating your 🌱 Ed25519 keypair... ok Creating your 🌱 Ed25519 keypair...
ok Profile z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created. ok Profile did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created.
Your radicle Node ID is z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device. Your radicle Node ID is z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device.
@ -18,7 +18,8 @@ You can get the above information at all times using the `self` command:
``` ```
$ rad self $ rad self
ID z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi ID did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
Node ID z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
Key (hash) SHA256:UIedaL6Cxm6OUErh9GQUzzglSk7VpQlVTI1TAFB/HWA Key (hash) SHA256:UIedaL6Cxm6OUErh9GQUzzglSk7VpQlVTI1TAFB/HWA
Key (full) AAAAC3NzaC1lZDI1NTE5AAAAIHahWSBEpuT1ESZbynOmBNkLBSnR32Ar4woZqSV2YNH1 Key (full) AAAAC3NzaC1lZDI1NTE5AAAAIHahWSBEpuT1ESZbynOmBNkLBSnR32Ar4woZqSV2YNH1
Storage (git) [..]/storage Storage (git) [..]/storage

View File

@ -14,7 +14,7 @@ We want to add a new maintainer to the project to help out with the
work. work.
``` ```
$ rad delegate add z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji $ rad delegate add did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
Added delegate 'did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG' Added delegate 'did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG'
ok Update successful! ok Update successful!
``` ```
@ -33,7 +33,7 @@ And finally, we no longer want to be part of the project so we pass on
the torch and remove ourselves from the delegate set. the torch and remove ourselves from the delegate set.
``` ```
$ rad delegate remove z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji $ rad delegate remove did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
Removed delegate 'did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi' Removed delegate 'did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
ok Update successful! ok Update successful!
``` ```

View File

@ -5,7 +5,7 @@ First off, we will create two proposals -- we can imagine two
delegates creating proposals concurrently. delegates creating proposals concurrently.
``` ```
$ rad id edit --title "Add Alice" --description "Add Alice as a delegate" --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm $ rad id edit --title "Add Alice" --description "Add Alice as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal '57332790a2eabc0b2fd8c7ff48c3579d5812d405' created 🌱 ok Identity proposal '57332790a2eabc0b2fd8c7ff48c3579d5812d405' created 🌱
title: Add Alice title: Add Alice
description: Add Alice as a delegate description: Add Alice as a delegate
@ -47,7 +47,7 @@ Quorum Reached
``` ```
``` ```
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --no-confirm $ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --no-confirm
ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' created 🌱 ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' created 🌱
title: Add Bob title: Add Bob
description: Add Bob as a delegate description: Add Bob as a delegate
@ -294,7 +294,7 @@ Quorum Reached
We can now update the proposal to have both keys in the delegates set: We can now update the proposal to have both keys in the delegates set:
``` ```
$ rad id update c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e --rev z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4 --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm $ rad id update c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e --rev z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4 --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' updated 🌱 ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' updated 🌱
ok Revision 'z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6' ok Revision 'z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6'
title: Add Bob title: Add Bob

View File

@ -9,11 +9,11 @@ delegate` command. For cases where `threshold > 1`, it is necessary to
gather a quorum of signatures to update the Radicle identity. To do gather a quorum of signatures to update the Radicle identity. To do
this, we use the `rad id` command. this, we use the `rad id` command.
Let's add Bob as a delegate using their key Let's add Bob as a delegate using their DID
`z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn`. `did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn`.
``` ```
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm $ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal '06d9efa2a9aad06bfdf25a25690e1ec7db2c3c39' created 🌱 ok Identity proposal '06d9efa2a9aad06bfdf25a25690e1ec7db2c3c39' created 🌱
title: Add Bob title: Add Bob
description: Add Bob as a delegate description: Add Bob as a delegate

View File

@ -22,20 +22,20 @@ others to work on. This is to ensure work is not duplicated.
Let's assign ourselves to this one. Let's assign ourselves to this one.
``` ```
$ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi $ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
It will now show in the list of issues assigned to us. It will now show in the list of issues assigned to us.
``` ```
$ rad issue list --assigned $ rad issue list --assigned
2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
Note: this can always be undone with the `unassign` subcommand. Note: this can always be undone with the `unassign` subcommand.
``` ```
$ rad unassign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi $ rad unassign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
Great, now we have communicated to the world about our car's defect. Great, now we have communicated to the world about our car's defect.

View File

@ -2,6 +2,7 @@ use std::ffi::OsString;
use std::str::FromStr; use std::str::FromStr;
use anyhow::anyhow; use anyhow::anyhow;
use radicle::prelude::Did;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args; use crate::terminal::args;
@ -16,7 +17,7 @@ pub const HELP: args::Help = args::Help {
usage: r#" usage: r#"
Usage Usage
rad assign <issue> <peer> rad assign <issue> <did>
Options Options
@ -27,7 +28,7 @@ Options
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub id: issue::IssueId, pub id: issue::IssueId,
pub peer: cob::ActorId, pub peer: Did,
} }
impl args::Args for Options { impl args::Args for Options {
@ -36,7 +37,7 @@ impl args::Args for Options {
let mut parser = lexopt::Parser::from_args(args); let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<issue::IssueId> = None; let mut id: Option<issue::IssueId> = None;
let mut peer: Option<cob::ActorId> = None; let mut peer: Option<Did> = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -52,12 +53,7 @@ impl args::Args for Options {
id = Some(val); id = Some(val);
} else if peer.is_none() { } else if peer.is_none() {
let val = val.to_string_lossy(); peer = Some(term::args::did(val)?);
let Ok(val) = cob::ActorId::from_str(&val) else {
return Err(anyhow!("invalid peer ID '{}'", val));
};
peer = Some(val);
} else { } else {
return Err(anyhow!(arg.unexpected())); return Err(anyhow!(arg.unexpected()));
} }
@ -90,7 +86,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
cob::store::Error::NotFound(_, _) => anyhow!("issue not found '{}'", options.id), cob::store::Error::NotFound(_, _) => anyhow!("issue not found '{}'", options.id),
_ => err.into(), _ => err.into(),
})?; })?;
issue.assign(vec![options.peer], &signer)?; issue.assign(vec![*options.peer], &signer)?;
Ok(()) Ok(())
} }

View File

@ -87,7 +87,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {
term::success!( term::success!(
"Profile {} created.", "Profile {} created.",
term::format::highlight(profile.id().to_string()) term::format::highlight(profile.did().to_string())
); );
term::blank(); term::blank();

View File

@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::identity::Id; use radicle::identity::Id;
use radicle_crypto::PublicKey; use radicle::prelude::Did;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
@ -23,7 +23,7 @@ pub const HELP: Help = Help {
usage: r#" usage: r#"
Usage Usage
rad delegate (add|remove) <public key> [--to <id>] rad delegate (add|remove) <did> [--to <id>]
rad delegate list [<id>] rad delegate list [<id>]
The `add` and `remove` commands are limited to managing delegates The `add` and `remove` commands are limited to managing delegates
@ -47,8 +47,8 @@ pub enum OperationName {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Operation { pub enum Operation {
Add { id: Option<Id>, key: PublicKey }, Add { id: Option<Id>, did: Did },
Remove { id: Option<Id>, key: PublicKey }, Remove { id: Option<Id>, did: Did },
List { id: Option<Id> }, List { id: Option<Id> },
} }
@ -64,7 +64,7 @@ impl Args for Options {
let mut parser = lexopt::Parser::from_args(args); let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<Id> = None; let mut id: Option<Id> = None;
let mut op: Option<OperationName> = None; let mut op: Option<OperationName> = None;
let mut key: Option<PublicKey> = None; let mut did: Option<Did> = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -81,27 +81,21 @@ impl Args for Options {
unknown => anyhow::bail!("unknown operation '{}'", unknown), unknown => anyhow::bail!("unknown operation '{}'", unknown),
}, },
Value(val) if op.is_some() => { Value(val) if op.is_some() => match op {
let val = val.to_string_lossy(); Some(OperationName::Add) | Some(OperationName::Remove) => {
did = Some(term::args::did(&val)?);
match op {
Some(OperationName::Add) | Some(OperationName::Remove) => {
if let Ok(val) = PublicKey::from_str(&val) {
key = Some(val);
} else {
return Err(anyhow!("invalid Public Key '{}'", val));
}
}
Some(OperationName::List) => {
if let Ok(val) = Id::from_str(&val) {
id = Some(val);
} else {
return Err(anyhow!("invalid Project ID '{}'", val));
}
}
None => continue,
} }
} Some(OperationName::List) => {
// TODO: create args::project_id function
let val = val.to_string_lossy();
if let Ok(val) = Id::from_str(&val) {
id = Some(val);
} else {
return Err(anyhow!("invalid Project ID '{}'", val));
}
}
None => continue,
},
_ => return Err(anyhow!(arg.unexpected())), _ => return Err(anyhow!(arg.unexpected())),
} }
} }
@ -110,11 +104,11 @@ impl Args for Options {
OperationName::List => Operation::List { id }, OperationName::List => Operation::List { id },
OperationName::Add => Operation::Add { OperationName::Add => Operation::Add {
id, id,
key: key.ok_or_else(|| anyhow!("a delegate key must be provided"))?, did: did.ok_or_else(|| anyhow!("a delegate DID must be provided"))?,
}, },
OperationName::Remove => Operation::Remove { OperationName::Remove => Operation::Remove {
id, id,
key: key.ok_or_else(|| anyhow!("a delegate key must be provided"))?, did: did.ok_or_else(|| anyhow!("a delegate DID must be provided"))?,
}, },
}; };
@ -127,8 +121,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let storage = &profile.storage; let storage = &profile.storage;
match options.op { match options.op {
Operation::Add { id, key } => add::run(&profile, storage, get_id(id)?, key)?, Operation::Add { id, did } => add::run(&profile, storage, get_id(id)?, *did)?,
Operation::Remove { id, key } => remove::run(&profile, storage, get_id(id)?, &key)?, Operation::Remove { id, did } => remove::run(&profile, storage, get_id(id)?, &did)?,
Operation::List { id } => list::run(&profile, storage, get_id(id)?)?, Operation::List { id } => list::run(&profile, storage, get_id(id)?)?,
} }

View File

@ -4,9 +4,9 @@ use anyhow::{anyhow, Context as _};
use radicle::cob::identity::{self, Proposal, ProposalId, Proposals, Revision, RevisionId}; use radicle::cob::identity::{self, Proposal, ProposalId, Proposals, Revision, RevisionId};
use radicle::git::Oid; use radicle::git::Oid;
use radicle::identity::Identity; use radicle::identity::Identity;
use radicle::prelude::Doc; use radicle::prelude::{Did, Doc};
use radicle::storage::ReadStorage as _; use radicle::storage::ReadStorage as _;
use radicle_crypto::{PublicKey, Verified}; use radicle_crypto::Verified;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
use crate::terminal::{self as term, Interactive}; use crate::terminal::{self as term, Interactive};
@ -19,7 +19,7 @@ pub const HELP: Help = Help {
Usage Usage
rad id (update|edit) [--title|-t] [--description|-d] rad id (update|edit) [--title|-t] [--description|-d]
[--delegates <key>] [--threshold <num>] [--delegates <did>] [--threshold <num>]
[--no-confirm] [--no-confirm]
rad id list rad id list
rad id (show|rebase) <id> [--rev <revision id>] rad id (show|rebase) <id> [--rev <revision id>]
@ -61,7 +61,7 @@ pub enum Operation {
Edit { Edit {
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
delegates: Vec<PublicKey>, delegates: Vec<Did>,
threshold: Option<usize>, threshold: Option<usize>,
}, },
Update { Update {
@ -69,7 +69,7 @@ pub enum Operation {
rev: Option<RevisionId>, rev: Option<RevisionId>,
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
delegates: Vec<PublicKey>, delegates: Vec<Did>,
threshold: Option<usize>, threshold: Option<usize>,
}, },
Rebase { Rebase {
@ -121,7 +121,7 @@ impl Args for Options {
let mut rev: Option<RevisionId> = None; let mut rev: Option<RevisionId> = None;
let mut title: Option<String> = None; let mut title: Option<String> = None;
let mut description: Option<String> = None; let mut description: Option<String> = None;
let mut delegates: Vec<PublicKey> = Vec::new(); let mut delegates: Vec<Did> = Vec::new();
let mut threshold: Option<usize> = None; let mut threshold: Option<usize> = None;
let mut interactive = Interactive::Yes; let mut interactive = Interactive::Yes;
let mut show_revisions = false; let mut show_revisions = false;
@ -161,11 +161,8 @@ impl Args for Options {
); );
} }
Long("delegates") => { Long("delegates") => {
let val = String::from(parser.value()?.to_string_lossy()); let did = term::args::did(&parser.value()?)?;
delegates.push( delegates.push(did);
PublicKey::from_str(&val)
.map_err(|_| anyhow!("invalid Public Key '{}'", val))?,
)
} }
Long("threshold") => { Long("threshold") => {
threshold = Some(parser.value()?.to_string_lossy().parse()?); threshold = Some(parser.value()?.to_string_lossy().parse()?);
@ -277,9 +274,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let proposed = { let proposed = {
let mut proposed = previous.doc.clone(); let mut proposed = previous.doc.clone();
proposed.threshold = threshold.unwrap_or(proposed.threshold); proposed.threshold = threshold.unwrap_or(proposed.threshold);
proposed proposed.delegates.extend(delegates);
.delegates
.extend(delegates.into_iter().map(|k| k.into()));
proposed proposed
}; };
@ -320,9 +315,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let proposed = { let proposed = {
let mut proposed = revision.proposed.clone(); let mut proposed = revision.proposed.clone();
proposed.threshold = threshold.unwrap_or(revision.proposed.threshold); proposed.threshold = threshold.unwrap_or(revision.proposed.threshold);
proposed proposed.delegates.extend(delegates);
.delegates
.extend(delegates.into_iter().map(|k| k.into()));
proposed proposed
}; };

View File

@ -3,6 +3,7 @@ use std::ffi::OsString;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::prelude::Did;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
@ -22,7 +23,7 @@ Usage
rad issue rad issue
rad issue delete <id> rad issue delete <id>
rad issue list [--assigned <key>] rad issue list [--assigned <did>]
rad issue open [--title <title>] [--description <text>] rad issue open [--title <title>] [--description <text>]
rad issue react <id> [--emoji <char>] rad issue react <id> [--emoji <char>]
rad issue show <id> rad issue show <id>
@ -38,7 +39,7 @@ Options
pub struct Metadata { pub struct Metadata {
title: String, title: String,
labels: Vec<Tag>, labels: Vec<Tag>,
assignees: Vec<cob::ActorId>, assignees: Vec<Did>,
} }
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
@ -57,7 +58,7 @@ pub enum OperationName {
pub enum Assigned { pub enum Assigned {
#[default] #[default]
Me, Me,
Peer(cob::ActorId), Peer(Did),
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -135,10 +136,7 @@ impl Args for Options {
} }
Long("assigned") | Short('a') if assigned.is_none() => { Long("assigned") | Short('a') if assigned.is_none() => {
if let Ok(val) = parser.value() { if let Ok(val) = parser.value() {
let val = val.to_string_lossy(); let peer = term::args::did(&val)?;
let Ok(peer) = cob::ActorId::from_str(&val) else {
return Err(anyhow!("invalid peer ID '{}'", val));
};
assigned = Some(Assigned::Peer(peer)); assigned = Some(Assigned::Peer(peer));
} else { } else {
assigned = Some(Assigned::Me); assigned = Some(Assigned::Me);
@ -265,7 +263,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
&meta.title, &meta.title,
description.trim(), description.trim(),
meta.labels.as_slice(), meta.labels.as_slice(),
meta.assignees.as_slice(), meta.assignees
.into_iter()
.map(cob::ActorId::from)
.collect::<Vec<_>>()
.as_slice(),
&signer, &signer,
)?; )?;
} }
@ -273,7 +275,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
Operation::List { assigned } => { Operation::List { assigned } => {
let assignee = match assigned { let assignee = match assigned {
Some(Assigned::Me) => Some(*profile.id()), Some(Assigned::Me) => Some(*profile.id()),
Some(Assigned::Peer(id)) => Some(id), Some(Assigned::Peer(id)) => Some(id.into()),
None => None, None => None,
}; };
@ -282,7 +284,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let (id, issue, _) = result?; let (id, issue, _) = result?;
let assigned: Vec<_> = issue.assigned().collect(); let assigned: Vec<_> = issue.assigned().collect();
if Some(true) == assignee.map(|a| !assigned.contains(&&a)) { if Some(true) == assignee.map(|a| !assigned.contains(&Did::from(a))) {
continue; continue;
} }

View File

@ -77,8 +77,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn all(profile: &Profile) -> anyhow::Result<()> { fn all(profile: &Profile) -> anyhow::Result<()> {
let mut table = term::Table::default(); let mut table = term::Table::default();
let did = profile.did();
table.push(["ID", &term::format::tertiary(did)]);
let node_id = profile.id(); let node_id = profile.id();
table.push(["ID", &term::format::tertiary(node_id)]); table.push(["Node ID", &term::format::tertiary(node_id)]);
let ssh_short = ssh::fmt::fingerprint(node_id); let ssh_short = ssh::fmt::fingerprint(node_id);
table.push(["Key (hash)", &term::format::tertiary(ssh_short)]); table.push(["Key (hash)", &term::format::tertiary(ssh_short)]);

View File

@ -2,6 +2,7 @@ use std::ffi::OsString;
use std::str::FromStr; use std::str::FromStr;
use anyhow::anyhow; use anyhow::anyhow;
use radicle::prelude::Did;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
@ -27,7 +28,7 @@ Options
#[derive(Debug)] #[derive(Debug)]
pub struct Options { pub struct Options {
pub id: issue::IssueId, pub id: issue::IssueId,
pub peer: cob::ActorId, pub peer: Did,
} }
impl Args for Options { impl Args for Options {
@ -36,7 +37,7 @@ impl Args for Options {
let mut parser = lexopt::Parser::from_args(args); let mut parser = lexopt::Parser::from_args(args);
let mut id: Option<issue::IssueId> = None; let mut id: Option<issue::IssueId> = None;
let mut peer: Option<cob::ActorId> = None; let mut peer: Option<Did> = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -52,12 +53,7 @@ impl Args for Options {
id = Some(val); id = Some(val);
} else if peer.is_none() { } else if peer.is_none() {
let val = val.to_string_lossy(); peer = Some(term::args::did(val)?);
let Ok(val) = cob::ActorId::from_str(&val) else {
return Err(anyhow!("invalid peer ID '{}'", val));
};
peer = Some(val);
} else { } else {
return Err(anyhow!(arg.unexpected())); return Err(anyhow!(arg.unexpected()));
} }
@ -90,7 +86,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
cob::store::Error::NotFound(_, _) => anyhow!("issue '{}' not found", options.id), cob::store::Error::NotFound(_, _) => anyhow!("issue '{}' not found", options.id),
_ => err.into(), _ => err.into(),
})?; })?;
issue.unassign(vec![options.peer], &signer)?; issue.unassign(vec![*options.peer], &signer)?;
Ok(()) Ok(())
} }

View File

@ -74,7 +74,7 @@ pub fn finish(unparsed: Vec<OsString>) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub fn did(val: OsString) -> anyhow::Result<Did> { pub fn did(val: &OsString) -> anyhow::Result<Did> {
let val = val.to_string_lossy(); let val = val.to_string_lossy();
let Ok(peer) = Did::from_str(&val) else { let Ok(peer) = Did::from_str(&val) else {
if crypto::PublicKey::from_str(&val).is_ok() { if crypto::PublicKey::from_str(&val).is_ok() {

View File

@ -16,6 +16,7 @@ use crate::cob::thread;
use crate::cob::thread::{CommentId, Thread}; use crate::cob::thread::{CommentId, Thread};
use crate::cob::{store, ActorId, ObjectId, OpId, TypeName}; use crate::cob::{store, ActorId, ObjectId, OpId, TypeName};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::{PublicKey, Signer};
use crate::prelude::Did;
use crate::storage::git as storage; use crate::storage::git as storage;
/// Issue operation. /// Issue operation.
@ -152,8 +153,8 @@ impl store::FromHistory for Issue {
} }
impl Issue { impl Issue {
pub fn assigned(&self) -> impl Iterator<Item = &ActorId> { pub fn assigned(&self) -> impl Iterator<Item = Did> + '_ {
self.assignees.iter() self.assignees.iter().map(Did::from)
} }
pub fn title(&self) -> &str { pub fn title(&self) -> &str {
@ -516,21 +517,21 @@ mod test {
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();
assert_eq!(1, assignees.len()); assert_eq!(1, assignees.len());
assert!(assignees.contains(&assignee)); assert!(assignees.contains(&Did::from(assignee)));
let mut issue = issues.get_mut(&id).unwrap(); let mut issue = issues.get_mut(&id).unwrap();
issue.assign([assignee_two], &signer).unwrap(); issue.assign([assignee_two], &signer).unwrap();
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();
assert_eq!(2, assignees.len()); assert_eq!(2, assignees.len());
assert!(assignees.contains(&assignee)); assert!(assignees.contains(&Did::from(assignee)));
assert!(assignees.contains(&assignee_two)); assert!(assignees.contains(&Did::from(assignee_two)));
} }
#[test] #[test]
@ -556,11 +557,11 @@ mod test {
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();
assert_eq!(2, assignees.len()); assert_eq!(2, assignees.len());
assert!(assignees.contains(&assignee)); assert!(assignees.contains(&Did::from(assignee)));
assert!(assignees.contains(&assignee_two)); assert!(assignees.contains(&Did::from(assignee_two)));
} }
#[test] #[test]
@ -641,10 +642,10 @@ mod test {
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();
assert_eq!(1, assignees.len()); assert_eq!(1, assignees.len());
assert!(assignees.contains(&assignee_two)); assert!(assignees.contains(&Did::from(assignee_two)));
} }
#[test] #[test]

View File

@ -19,6 +19,7 @@ use crate::crypto::ssh::agent::Agent;
use crate::crypto::ssh::{keystore, Keystore, Passphrase}; use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::{PublicKey, Signer};
use crate::node; use crate::node;
use crate::prelude::Did;
use crate::storage::git::transport; use crate::storage::git::transport;
use crate::storage::git::Storage; use crate::storage::git::Storage;
@ -103,6 +104,10 @@ impl Profile {
&self.public_key &self.public_key
} }
pub fn did(&self) -> Did {
Did::from(self.public_key)
}
pub fn signer(&self) -> Result<Box<dyn Signer>, Error> { pub fn signer(&self) -> Result<Box<dyn Signer>, Error> {
if let Some(passphrase) = env::read_passphrase() { if let Some(passphrase) = env::read_passphrase() {
let signer = keystore::MemorySigner::load(&self.keystore, passphrase)?; let signer = keystore::MemorySigner::load(&self.keystore, passphrase)?;