From 1cacf1bce77b88febc0cf9e2f0f9f468d4b30555 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 1 Feb 2024 09:59:09 +0000 Subject: feat(repo_ref)!: use nip34 kind and tags - change kind number - do not rely on d identifiers for unique commit id. set it as default to unique commit id shorthand. - remove "r-" prefix from unique commit id r tag and instead add checks for SHA1 validity - rename tag git_server to clone - add web tag - use single relays tag instead of multiple relay tags BREAKING CHANGE: change repo event kind and tags to reflect nip34 draft. events with the older kind will no longer be found and will not be in a valid format --- src/repo_ref.rs | 217 +++++++++++++++++++++++++++++++++++----------- src/sub_commands/claim.rs | 14 +++ test_utils/src/lib.rs | 35 ++++++-- tests/claim.rs | 63 ++++++++------ 4 files changed, 244 insertions(+), 85 deletions(-) diff --git a/src/repo_ref.rs b/src/repo_ref.rs index 22236f7..1ec0ece 100644 --- a/src/repo_ref.rs +++ b/src/repo_ref.rs @@ -17,8 +17,10 @@ use crate::{ pub struct RepoRef { pub name: String, pub description: String, + pub identifier: String, pub root_commit: String, pub git_server: String, + pub web: Vec, pub relays: Vec, pub maintainers: Vec, // code languages and hashtags @@ -33,6 +35,10 @@ impl TryFrom for RepoRef { } let mut r = Self::default(); + if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { + r.identifier = t.as_vec()[1].clone(); + } + if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("name")) { r.name = t.as_vec()[1].clone(); } @@ -41,20 +47,27 @@ impl TryFrom for RepoRef { r.description = t.as_vec()[1].clone(); } - if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("git-server")) { + if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("clone")) { r.git_server = t.as_vec()[1].clone(); } - if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { + if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("web")) { + r.web = t.as_vec().clone(); + r.web.remove(0); + } + + if let Some(t) = event.tags.iter().find(|t| { + t.as_vec()[0].eq("r") + && t.as_vec()[1].len().eq(&40) + && git2::Oid::from_str(t.as_vec()[1].as_str()).is_ok() + }) { r.root_commit = t.as_vec()[1].clone(); } - r.relays = event - .tags - .iter() - .filter(|t| t.as_vec()[0].eq("relay")) - .map(|t| t.as_vec()[1].clone()) - .collect(); + if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("relays")) { + r.relays = t.as_vec().clone(); + r.relays.remove(0); + } for tag in event.tags.iter().filter(|t| t.as_vec()[0].eq("p")) { let pk = tag.as_vec()[1].clone(); @@ -68,7 +81,8 @@ impl TryFrom for RepoRef { Ok(r) } } -static REPO_REF_KIND: u64 = 30_317; + +pub static REPO_REF_KIND: u64 = 30_617; impl RepoRef { pub fn to_event(&self, keys: &nostr::Keys) -> Result { @@ -77,17 +91,41 @@ impl RepoRef { "", [ vec![ - Tag::Identifier(self.root_commit.to_string()), - Tag::Reference(format!("r-{}", self.root_commit)), + Tag::Identifier(if self.identifier.to_string().is_empty() { + // fiatjaf thought a random string. its not in the draft nip. + // thread_rng() + // .sample_iter(&Alphanumeric) + // .take(15) + // .map(char::from) + // .collect() + + // an identifier based on first commit is better so that users dont + // accidentally create two seperate identifiers for the same repo + // there is a hesitancy to use the commit id + // in another conversaion with fiatjaf he suggested the first 6 character of + // the commit id + // here we are using 7 which is the standard for shorthand commit id + self.root_commit.to_string()[..7].to_string() + } else { + self.identifier.to_string() + }), + Tag::Reference(self.root_commit.to_string()), Tag::Name(self.name.clone()), Tag::Description(self.description.clone()), Tag::Generic( - nostr::TagKind::Custom("git-server".to_string()), + nostr::TagKind::Custom("clone".to_string()), vec![self.git_server.clone()], ), - Tag::Reference(self.git_server.clone()), + Tag::Generic(nostr::TagKind::Custom("web".to_string()), self.web.clone()), + Tag::Generic( + nostr::TagKind::Custom("relays".to_string()), + self.relays.clone(), + ), ], - self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), + // this appears like a number of relay tags but test suggest is is actually + // what we want which is a relays tag with lots of values. no need for the + // change? + // self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), self.maintainers .iter() .map(|pk| Tag::public_key(*pk)) @@ -116,7 +154,7 @@ pub async fn fetch( let mut repo_event_filter = nostr::Filter::default() .kind(nostr::Kind::Custom(REPO_REF_KIND)) - .identifier(root_commit); + .reference(root_commit); let mut relays = fallback_relays; if let Ok(repo_config) = repo_config { @@ -127,6 +165,15 @@ pub async fn fetch( let events: Vec = client.get_events(relays, vec![repo_event_filter]).await?; + // TODO: fallback to ask user for nevent - to capture + + // TODO: if maintainers.yaml isn't present, as the user to select from the + // pubkeys they want to use. could use WoT as an indicator as well as the repo + // and user name. + + // TODO: if maintainers.yaml isn't present, save the selected repo pubkey + // somewhere within .git folder for future use and seek to get that next time + RepoRef::try_from( events .iter() @@ -207,10 +254,15 @@ mod tests { fn create() -> nostr::Event { RepoRef { + identifier: "123412341".to_string(), name: "test name".to_string(), description: "test description".to_string(), - root_commit: "23471389461".to_string(), + root_commit: "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2".to_string(), git_server: "https://localhost:1000".to_string(), + web: vec![ + "https://exampleproject.xyz".to_string(), + "https://gitworkshop.dev/123".to_string(), + ], relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], } @@ -220,6 +272,11 @@ mod tests { mod try_from { use super::*; + #[test] + fn identifier() { + assert_eq!(RepoRef::try_from(create()).unwrap().identifier, "123412341",) + } + #[test] fn name() { assert_eq!(RepoRef::try_from(create()).unwrap().name, "test name",) @@ -234,13 +291,60 @@ mod tests { } #[test] - fn root_commit() { + fn root_commit_is_r_tag() { assert_eq!( RepoRef::try_from(create()).unwrap().root_commit, - "23471389461", + "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2", ) } + mod root_commit_is_empty_if_no_r_tag_which_is_sha1_format { + use nostr::JsonUtil; + + use super::*; + fn create_with_incorrect_first_commit_ref(s: &str) -> nostr::Event { + nostr::Event::from_json( + create() + .as_json() + .replace("5e664e5a7845cd1373c79f580ca4fe29ab5b34d2", s), + ) + .unwrap() + } + + #[test] + fn less_than_40_characters() { + let s = "5e664e5a7845cd1373"; + assert_eq!( + RepoRef::try_from(create_with_incorrect_first_commit_ref(s)) + .unwrap() + .root_commit, + "", + ) + } + + #[test] + fn more_than_40_characters() { + let s = "5e664e5a7845cd1373c79f580ca4fe29ab5b34d2111111111"; + assert_eq!( + RepoRef::try_from(create_with_incorrect_first_commit_ref(s)) + .unwrap() + .root_commit, + "", + ) + } + + #[test] + fn not_hex_characters() { + let s = "xxx64e5a7845cd1373c79f580ca4fe29ab5b34d2"; + assert_eq!( + RepoRef::try_from(create_with_incorrect_first_commit_ref(s)) + .unwrap() + .root_commit, + "", + ) + } + } + #[test] fn git_server() { assert_eq!( @@ -249,6 +353,17 @@ mod tests { ) } + #[test] + fn web() { + assert_eq!( + RepoRef::try_from(create()).unwrap().web, + vec![ + "https://exampleproject.xyz".to_string(), + "https://gitworkshop.dev/123".to_string() + ], + ) + } + #[test] fn relays() { assert_eq!( @@ -272,67 +387,65 @@ mod tests { use super::*; #[test] - fn name() { + fn identifier() { assert!( create() .tags .iter() - .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("test name")) + .any(|t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("123412341")) ) } - #[test] - fn description() { - assert!(create().tags.iter().any( - |t| t.as_vec()[0].eq("description") && t.as_vec()[1].eq("test description") - )) - } #[test] - fn root_commit_as_d_replaceable_event_identifier() { + fn name() { assert!( create() .tags .iter() - .any(|t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("23471389461")) + .any(|t| t.as_vec()[0].eq("name") && t.as_vec()[1].eq("test name")) ) } - #[test] - fn git_server() { - assert!(create().tags.iter().any(|t| t.as_vec()[0].eq("git-server") - && t.as_vec()[1].eq("https://localhost:1000"))) + fn description() { + assert!(create().tags.iter().any( + |t| t.as_vec()[0].eq("description") && t.as_vec()[1].eq("test description") + )) } #[test] - fn git_server_as_reference() { - assert!( - create().tags.iter().any( - |t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("https://localhost:1000") - ) - ) + fn root_commit_as_reference() { + assert!(create().tags.iter().any(|t| t.as_vec()[0].eq("r") + && t.as_vec()[1].eq("5e664e5a7845cd1373c79f580ca4fe29ab5b34d2"))) } #[test] - fn root_commit_as_reference() { - assert!( - create() - .tags - .iter() - .any(|t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("r-23471389461")) - ) + fn git_server() { + assert!(create().tags.iter().any( + |t| t.as_vec()[0].eq("clone") && t.as_vec()[1].eq("https://localhost:1000") + )) } #[test] fn relays() { let event = create(); - let relay_tags = event + let relays_tag: &nostr::Tag = event .tags .iter() - .filter(|t| t.as_vec()[0].eq("relay")) - .collect::>(); - assert_eq!(relay_tags[0].as_vec().len(), 2); - assert_eq!(relay_tags[0].as_vec()[1], "ws://relay1.io"); - assert_eq!(relay_tags[1].as_vec()[1], "ws://relay2.io"); + .find(|t| t.as_vec()[0].eq("relays")) + .unwrap(); + assert_eq!(relays_tag.as_vec().len(), 3); + assert_eq!(relays_tag.as_vec()[1], "ws://relay1.io"); + assert_eq!(relays_tag.as_vec()[2], "ws://relay2.io"); + } + + #[test] + fn web() { + let event = create(); + let web_tag: &nostr::Tag = + event.tags.iter().find(|t| t.as_vec()[0].eq("web")).unwrap(); + assert_eq!(web_tag.as_vec().len(), 3); + assert_eq!(web_tag.as_vec()[1], "https://exampleproject.xyz"); + assert_eq!(web_tag.as_vec()[2], "https://gitworkshop.dev/123"); } #[test] @@ -356,7 +469,7 @@ mod tests { #[test] fn no_other_tags() { - assert_eq!(create().tags.len(), 10) + assert_eq!(create().tags.len(), 9) } } } diff --git a/src/sub_commands/claim.rs b/src/sub_commands/claim.rs index ce39a64..05ad961 100644 --- a/src/sub_commands/claim.rs +++ b/src/sub_commands/claim.rs @@ -23,6 +23,9 @@ pub struct SubCommandArgs { /// optional description description: Option, #[clap(short, long, value_parser, num_args = 1..)] + /// homepage + web: Vec, + #[clap(short, long, value_parser, num_args = 1..)] /// relays contributors push patches and comments to relays: Vec, } @@ -62,6 +65,15 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { ) .context("no git remote origin configured")?; + let web: Vec = if args.web.is_empty() { + Interactor::default() + .input(PromptInputParms::default().with_prompt("description (Optional)"))? + .split(' ') + .map(std::string::ToString::to_string) + .collect() + } else { + args.web.clone() + }; #[cfg(not(test))] let mut client = Client::default(); #[cfg(test)] @@ -120,10 +132,12 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { println!("publishing repostory reference..."); let repo_event = RepoRef { + identifier: root_commit.to_string()[..7].to_string(), name, description, root_commit: root_commit.to_string(), git_server, + web, relays: repo_relays.clone(), maintainers, } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index f3f9dcf..9996008 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -13,7 +13,7 @@ pub mod relay; pub static PR_KIND: u64 = 318; pub static PATCH_KIND: u64 = 317; -pub static REPOSITORY_KIND: u64 = 30317; +pub static REPOSITORY_KIND: u64 = 30617; pub static TEST_KEY_1_NSEC: &str = "nsec1ppsg5sm2aexq06juxmu9evtutr6jkwkhp98exxxvwamhru9lyx9s3rwseq"; @@ -132,17 +132,38 @@ pub fn make_event_old_or_change_user( pub fn generate_repo_ref_event() -> nostr::Event { // taken from test git_repo + // TODO - this may not be consistant across computers as it might take the + // author and committer from global git config let root_commit = "9ee507fc4357d7ee16a5d8901bedcd103f23c17d"; nostr::event::EventBuilder::new( nostr::Kind::Custom(REPOSITORY_KIND), "", [ - Tag::Identifier(root_commit.to_string()), - Tag::Reference(format!("r-{}", root_commit)), - Tag::Name("example name".to_string()), - Tag::Description("example description".to_string()), - Tag::Relay("ws://localhost:8055".into()), - Tag::Relay("ws://localhost:8056".into()), + Tag::Identifier( + // root_commit.to_string() + format!("{}-consider-it-random", root_commit), + ), + Tag::Reference(root_commit.into()), + Tag::Name("example name".into()), + Tag::Description("example description".into()), + Tag::Generic( + nostr::TagKind::Custom("clone".to_string()), + vec!["git:://123.gitexample.com/test".to_string()], + ), + Tag::Generic( + nostr::TagKind::Custom("web".to_string()), + vec![ + "https://exampleproject.xyz".to_string(), + "https://gitworkshop.dev/123".to_string(), + ], + ), + Tag::Generic( + nostr::TagKind::Custom("relays".to_string()), + vec![ + "ws://localhost:8055".to_string(), + "ws://localhost:8056".to_string(), + ], + ), Tag::public_key(TEST_KEY_1_KEYS.public_key()), Tag::public_key(TEST_KEY_2_KEYS.public_key()), ], diff --git a/tests/claim.rs b/tests/claim.rs index cd845f7..9eb4b8a 100644 --- a/tests/claim.rs +++ b/tests/claim.rs @@ -51,6 +51,9 @@ mod when_repo_not_previously_claimed { "example-name", "--description", "example-description", + "--web", + "https://exampleproject.xyz", + "https://gitworkshop.dev/123", "--relays", "ws://localhost:8055", "ws://localhost:8056", @@ -251,7 +254,7 @@ mod when_repo_not_previously_claimed { #[test] #[serial] - fn root_commit_as_d_replaceable_event_identifier() -> Result<()> { + fn d_replaceable_event_identifier_defaults_to_root_commit_id_shorthand() -> Result<()> { let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; for relay in [&r53, &r55, &r56] { let event: &nostr::Event = relay @@ -260,8 +263,12 @@ mod when_repo_not_previously_claimed { .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("d") - && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); + assert!( + event + .tags + .iter() + .any(|t| t.as_vec()[0].eq("d") && t.as_vec()[1].eq("9ee507f")) + ); } Ok(()) } @@ -277,9 +284,8 @@ mod when_repo_not_previously_claimed { .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - // root commit 'r' tag with 'r-' prefix assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("r") - && t.as_vec()[1].eq("r-9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); + && t.as_vec()[1].eq("9ee507fc4357d7ee16a5d8901bedcd103f23c17d"))); } Ok(()) } @@ -333,15 +339,17 @@ mod when_repo_not_previously_claimed { .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("git-server") - && t.as_vec()[1].eq("https://localhost:1000"))); + assert!( + event.tags.iter().any(|t| t.as_vec()[0].eq("clone") + && t.as_vec()[1].eq("https://localhost:1000")) + ); } Ok(()) } #[test] #[serial] - fn git_server_as_reference() -> Result<()> { + fn relays() -> Result<()> { let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; for relay in [&r53, &r55, &r56] { let event: &nostr::Event = relay @@ -349,17 +357,21 @@ mod when_repo_not_previously_claimed { .iter() .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - - assert!(event.tags.iter().any( - |t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("https://localhost:1000") - )); + let relays_tag = event + .tags + .iter() + .find(|t| t.as_vec()[0].eq("relays")) + .unwrap() + .as_vec(); + assert_eq!(relays_tag[1], "ws://localhost:8055",); + assert_eq!(relays_tag[2], "ws://localhost:8056",); } Ok(()) } #[test] #[serial] - fn relays() -> Result<()> { + fn web() -> Result<()> { let (_, _, r53, r55, r56) = futures::executor::block_on(prep_run_claim())?; for relay in [&r53, &r55, &r56] { let event: &nostr::Event = relay @@ -367,14 +379,14 @@ mod when_repo_not_previously_claimed { .iter() .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - - let relay_tags = event + let web_tag = event .tags .iter() - .filter(|t| t.as_vec()[0].eq("relay")) - .collect::>(); - assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8055"); - assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8056"); + .find(|t| t.as_vec()[0].eq("web")) + .unwrap() + .as_vec(); + assert_eq!(web_tag[1], "https://exampleproject.xyz",); + assert_eq!(web_tag[2], "https://gitworkshop.dev/123",); } Ok(()) } @@ -501,6 +513,9 @@ mod when_repo_not_previously_claimed { "example-name", "--description", "example-description", + "--web", + "https://exampleproject.xyz", + "https://gitworkshop.dev/123", ], ) } @@ -572,13 +587,9 @@ mod when_repo_not_previously_claimed { .find(|e| e.kind.as_u64().eq(&REPOSITORY_KIND)) .unwrap(); - let relay_tags = event - .tags - .iter() - .filter(|t| t.as_vec()[0].eq("relay")) - .collect::>(); - assert_eq!(relay_tags[0].as_vec()[1], "ws://localhost:8053"); - assert_eq!(relay_tags[1].as_vec()[1], "ws://localhost:8055"); + assert!(event.tags.iter().any(|t| t.as_vec()[0].eq("relays") + && t.as_vec()[1].eq("ws://localhost:8053") + && t.as_vec()[2].eq("ws://localhost:8055"))); } Ok(()) } -- cgit v1.2.3