diff options
| author | Laszlo Megyer <lez@github.com> | 2024-12-16 11:38:06 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-12-16 16:50:39 +0000 |
| commit | cc87576cdfdd3aa18df6e94fbfa079d9e4f0241a (patch) | |
| tree | a8c2bdb2333b75b7ead07dc6253635acf7a9c711 | |
| parent | 4ee83e2fe5335a8afd78439c35f029c4a472e797 (diff) | |
feat(init): default to nip05 git nostr url
If the user has NIP-05 set up in profile, and it resolves at the time
of running `ngit init`, NIP-05 will be used in the nostr remote url.
| -rw-r--r-- | src/bin/ngit/sub_commands/init.rs | 81 | ||||
| -rw-r--r-- | src/lib/git/nostr_url.rs | 12 | ||||
| -rw-r--r-- | src/lib/login/user.rs | 8 | ||||
| -rw-r--r-- | src/lib/repo_ref.rs | 10 |
4 files changed, 97 insertions, 14 deletions
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs index 80e182b..f26eea3 100644 --- a/src/bin/ngit/sub_commands/init.rs +++ b/src/bin/ngit/sub_commands/init.rs | |||
| @@ -1,9 +1,18 @@ | |||
| 1 | use std::collections::HashMap; | 1 | use std::collections::{HashMap, HashSet}; |
| 2 | 2 | ||
| 3 | use anyhow::{Context, Result}; | 3 | use anyhow::{Context, Result}; |
| 4 | use console::Style; | 4 | use console::{Style, Term}; |
| 5 | use ngit::{cli_interactor::PromptConfirmParms, git::nostr_url::NostrUrlDecoded}; | 5 | use ngit::{ |
| 6 | use nostr::{FromBech32, PublicKey, ToBech32, nips::nip01::Coordinate}; | 6 | cli_interactor::PromptConfirmParms, |
| 7 | git::nostr_url::{NostrUrlDecoded, save_nip05_to_git_config_cache}, | ||
| 8 | }; | ||
| 9 | use nostr::{ | ||
| 10 | FromBech32, PublicKey, ToBech32, | ||
| 11 | nips::{ | ||
| 12 | nip01::Coordinate, | ||
| 13 | nip05::{self}, | ||
| 14 | }, | ||
| 15 | }; | ||
| 7 | use nostr_sdk::{Kind, RelayUrl}; | 16 | use nostr_sdk::{Kind, RelayUrl}; |
| 8 | 17 | ||
| 9 | use crate::{ | 18 | use crate::{ |
| @@ -397,7 +406,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 397 | 406 | ||
| 398 | println!("publishing repostory reference..."); | 407 | println!("publishing repostory reference..."); |
| 399 | 408 | ||
| 400 | let repo_ref = RepoRef { | 409 | let mut repo_ref = RepoRef { |
| 401 | identifier: identifier.clone(), | 410 | identifier: identifier.clone(), |
| 402 | name, | 411 | name, |
| 403 | description, | 412 | description, |
| @@ -408,6 +417,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 408 | trusted_maintainer: user_ref.public_key, | 417 | trusted_maintainer: user_ref.public_key, |
| 409 | maintainers: maintainers.clone(), | 418 | maintainers: maintainers.clone(), |
| 410 | events: HashMap::new(), | 419 | events: HashMap::new(), |
| 420 | nostr_git_url: None, | ||
| 411 | }; | 421 | }; |
| 412 | let repo_event = repo_ref.to_event(&signer).await?; | 422 | let repo_event = repo_ref.to_event(&signer).await?; |
| 413 | 423 | ||
| @@ -437,19 +447,72 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | |||
| 437 | false, | 447 | false, |
| 438 | )?; | 448 | )?; |
| 439 | 449 | ||
| 440 | let relays = relays | 450 | // if nip05 valid, set nostr git url to use that format |
| 441 | .iter() | 451 | let hint_for_nip05_address = { |
| 442 | .map(std::string::ToString::to_string) | 452 | if let Some(nip05) = user_ref.metadata.nip05 { |
| 443 | .collect::<Vec<String>>(); | 453 | let term = Term::stdout(); |
| 454 | term.write_line(&format!("fetching nip05 details for {nip05}..."))?; | ||
| 455 | if let Ok(nprofile) = nip05::profile(nip05.clone(), None).await { | ||
| 456 | let _ = term.clear_last_lines(1); | ||
| 457 | let _ = | ||
| 458 | save_nip05_to_git_config_cache(&nip05, &nprofile.public_key, &Some(&git_repo)); | ||
| 459 | // Normalize URLs before doing the intersection. | ||
| 460 | let repo_relays: HashSet<RelayUrl> = relays | ||
| 461 | .iter() | ||
| 462 | .map(|r| RelayUrl::parse(r.as_str_without_trailing_slash()).unwrap()) | ||
| 463 | .collect(); | ||
| 464 | let nip05_relays: HashSet<RelayUrl> = nprofile | ||
| 465 | .relays | ||
| 466 | .iter() | ||
| 467 | .map(|r| RelayUrl::parse(r.as_str_without_trailing_slash()).unwrap()) | ||
| 468 | .collect(); | ||
| 469 | let mut inter = repo_relays.intersection(&nip05_relays); | ||
| 470 | |||
| 471 | repo_ref.set_nostr_git_url(NostrUrlDecoded { | ||
| 472 | original_string: String::new(), | ||
| 473 | nip05: Some(nip05.clone()), | ||
| 474 | coordinate: Coordinate { | ||
| 475 | kind: Kind::GitRepoAnnouncement, | ||
| 476 | public_key: user_ref.public_key, | ||
| 477 | identifier: repo_ref.identifier.clone(), | ||
| 478 | relays: if inter.next().is_some() || relays.is_empty() { | ||
| 479 | vec![] | ||
| 480 | } else { | ||
| 481 | vec![relays.first().unwrap().clone()] | ||
| 482 | }, | ||
| 483 | }, | ||
| 484 | protocol: None, | ||
| 485 | user: None, | ||
| 486 | }); | ||
| 487 | if inter.next().is_some() { | ||
| 488 | "note: point your NIP-05 relays to one of the repo relays for a cleaner nostr:// remote URL.".to_string() | ||
| 489 | } else { | ||
| 490 | String::new() | ||
| 491 | } | ||
| 492 | } else { | ||
| 493 | "note: could not validate your nip05 address {nip05} which could be used for a shorter nostr:// remote URL.".to_string() | ||
| 494 | } | ||
| 495 | } else { | ||
| 496 | String::new() | ||
| 497 | } | ||
| 498 | }; | ||
| 444 | 499 | ||
| 445 | prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo).await?; | 500 | prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo).await?; |
| 446 | 501 | ||
| 502 | if !hint_for_nip05_address.is_empty() { | ||
| 503 | println!("{hint_for_nip05_address}"); | ||
| 504 | } | ||
| 505 | |||
| 447 | // TODO: if no state event exists and there is currently a remote called | 506 | // TODO: if no state event exists and there is currently a remote called |
| 448 | // "origin", automtically push rather than waiting for the next commit | 507 | // "origin", automtically push rather than waiting for the next commit |
| 449 | 508 | ||
| 450 | // no longer create a new maintainers.yaml file - its too confusing for users | 509 | // no longer create a new maintainers.yaml file - its too confusing for users |
| 451 | // as it falls out of sync with data in nostr event . update if it already | 510 | // as it falls out of sync with data in nostr event . update if it already |
| 452 | // exists | 511 | // exists |
| 512 | let relays = relays | ||
| 513 | .iter() | ||
| 514 | .map(std::string::ToString::to_string) | ||
| 515 | .collect::<Vec<String>>(); | ||
| 453 | if match &repo_config_result { | 516 | if match &repo_config_result { |
| 454 | Ok(config) => { | 517 | Ok(config) => { |
| 455 | !<std::option::Option<std::string::String> as Clone>::clone(&config.identifier) | 518 | !<std::option::Option<std::string::String> as Clone>::clone(&config.identifier) |
diff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs index 6b38a93..de7953f 100644 --- a/src/lib/git/nostr_url.rs +++ b/src/lib/git/nostr_url.rs | |||
| @@ -55,7 +55,7 @@ impl FromStr for ServerProtocol { | |||
| 55 | } | 55 | } |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | #[derive(Debug, PartialEq)] | 58 | #[derive(Debug, PartialEq, Clone)] |
| 59 | pub struct NostrUrlDecoded { | 59 | pub struct NostrUrlDecoded { |
| 60 | pub original_string: String, | 60 | pub original_string: String, |
| 61 | pub coordinate: Coordinate, | 61 | pub coordinate: Coordinate, |
| @@ -73,7 +73,11 @@ impl fmt::Display for NostrUrlDecoded { | |||
| 73 | if let Some(protocol) = &self.protocol { | 73 | if let Some(protocol) = &self.protocol { |
| 74 | write!(f, "{}/", protocol)?; | 74 | write!(f, "{}/", protocol)?; |
| 75 | } | 75 | } |
| 76 | write!(f, "{}/", self.coordinate.public_key.to_bech32().unwrap())?; | 76 | if let Some(nip05) = &self.nip05 { |
| 77 | write!(f, "{}/", nip05)?; | ||
| 78 | } else { | ||
| 79 | write!(f, "{}/", self.coordinate.public_key.to_bech32().unwrap())?; | ||
| 80 | } | ||
| 77 | if let Some(relay) = self.coordinate.relays.first() { | 81 | if let Some(relay) = self.coordinate.relays.first() { |
| 78 | write!( | 82 | write!( |
| 79 | f, | 83 | f, |
| @@ -97,6 +101,7 @@ impl NostrUrlDecoded { | |||
| 97 | let mut protocol = None; | 101 | let mut protocol = None; |
| 98 | let mut user = None; | 102 | let mut user = None; |
| 99 | let mut relays = vec![]; | 103 | let mut relays = vec![]; |
| 104 | let mut nip05 = None; | ||
| 100 | 105 | ||
| 101 | if !url.starts_with("nostr://") { | 106 | if !url.starts_with("nostr://") { |
| 102 | bail!("nostr git url must start with nostr://"); | 107 | bail!("nostr git url must start with nostr://"); |
| @@ -157,7 +162,6 @@ impl NostrUrlDecoded { | |||
| 157 | } | 162 | } |
| 158 | // extract naddr npub/<optional-relays>/identifer | 163 | // extract naddr npub/<optional-relays>/identifer |
| 159 | let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; | 164 | let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; |
| 160 | let mut nip05 = None; | ||
| 161 | // naddr used | 165 | // naddr used |
| 162 | let coordinate = if let Ok(coordinate) = Coordinate::parse(part) { | 166 | let coordinate = if let Ok(coordinate) = Coordinate::parse(part) { |
| 163 | if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) { | 167 | if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) { |
| @@ -254,7 +258,7 @@ pub fn use_nip05_git_config_cache_to_find_nip05_from_public_key( | |||
| 254 | .cloned()) | 258 | .cloned()) |
| 255 | } | 259 | } |
| 256 | 260 | ||
| 257 | fn save_nip05_to_git_config_cache( | 261 | pub fn save_nip05_to_git_config_cache( |
| 258 | nip05: &str, | 262 | nip05: &str, |
| 259 | public_key: &PublicKey, | 263 | public_key: &PublicKey, |
| 260 | git_repo: &Option<&Repo>, | 264 | git_repo: &Option<&Repo>, |
diff --git a/src/lib/login/user.rs b/src/lib/login/user.rs index 107e765..071cb25 100644 --- a/src/lib/login/user.rs +++ b/src/lib/login/user.rs | |||
| @@ -22,6 +22,7 @@ pub struct UserRef { | |||
| 22 | pub struct UserMetadata { | 22 | pub struct UserMetadata { |
| 23 | pub name: String, | 23 | pub name: String, |
| 24 | pub created_at: Timestamp, | 24 | pub created_at: Timestamp, |
| 25 | pub nip05: Option<String>, | ||
| 25 | } | 26 | } |
| 26 | 27 | ||
| 27 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] | 28 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] |
| @@ -149,7 +150,7 @@ pub fn extract_user_metadata( | |||
| 149 | }; | 150 | }; |
| 150 | 151 | ||
| 151 | Ok(UserMetadata { | 152 | Ok(UserMetadata { |
| 152 | name: if let Some(metadata) = metadata { | 153 | name: if let Some(metadata) = metadata.clone() { |
| 153 | if let Some(n) = metadata.name { | 154 | if let Some(n) = metadata.name { |
| 154 | n | 155 | n |
| 155 | } else if let Some(n) = metadata.custom.get("displayName") { | 156 | } else if let Some(n) = metadata.custom.get("displayName") { |
| @@ -167,6 +168,11 @@ pub fn extract_user_metadata( | |||
| 167 | } else { | 168 | } else { |
| 168 | public_key.to_bech32()? | 169 | public_key.to_bech32()? |
| 169 | }, | 170 | }, |
| 171 | nip05: if let Some(metadata) = metadata { | ||
| 172 | metadata.nip05 | ||
| 173 | } else { | ||
| 174 | None | ||
| 175 | }, | ||
| 170 | created_at: if let Some(event) = event { | 176 | created_at: if let Some(event) = event { |
| 171 | event.created_at | 177 | event.created_at |
| 172 | } else { | 178 | } else { |
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs index a9d1186..8705597 100644 --- a/src/lib/repo_ref.rs +++ b/src/lib/repo_ref.rs | |||
| @@ -38,6 +38,7 @@ pub struct RepoRef { | |||
| 38 | pub maintainers: Vec<PublicKey>, | 38 | pub maintainers: Vec<PublicKey>, |
| 39 | pub trusted_maintainer: PublicKey, | 39 | pub trusted_maintainer: PublicKey, |
| 40 | pub events: HashMap<Coordinate, nostr::Event>, | 40 | pub events: HashMap<Coordinate, nostr::Event>, |
| 41 | pub nostr_git_url: Option<NostrUrlDecoded>, | ||
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef { | 44 | impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef { |
| @@ -60,6 +61,7 @@ impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef { | |||
| 60 | maintainers: Vec::new(), | 61 | maintainers: Vec::new(), |
| 61 | trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey), | 62 | trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey), |
| 62 | events: HashMap::new(), | 63 | events: HashMap::new(), |
| 64 | nostr_git_url: None, | ||
| 63 | }; | 65 | }; |
| 64 | 66 | ||
| 65 | for tag in event.tags.iter() { | 67 | for tag in event.tags.iter() { |
| @@ -235,7 +237,14 @@ impl RepoRef { | |||
| 235 | .collect::<Vec<(Coordinate, Option<Timestamp>)>>() | 237 | .collect::<Vec<(Coordinate, Option<Timestamp>)>>() |
| 236 | } | 238 | } |
| 237 | 239 | ||
| 240 | pub fn set_nostr_git_url(&mut self, nostr_git_url: NostrUrlDecoded) { | ||
| 241 | self.nostr_git_url = Some(nostr_git_url) | ||
| 242 | } | ||
| 243 | |||
| 238 | pub fn to_nostr_git_url(&self, git_repo: &Option<&Repo>) -> String { | 244 | pub fn to_nostr_git_url(&self, git_repo: &Option<&Repo>) -> String { |
| 245 | if let Some(nostr_git_url) = &self.nostr_git_url { | ||
| 246 | return nostr_git_url.original_string.clone(); | ||
| 247 | } | ||
| 239 | let c = self.coordinate_with_hint(); | 248 | let c = self.coordinate_with_hint(); |
| 240 | format!("{}", NostrUrlDecoded { | 249 | format!("{}", NostrUrlDecoded { |
| 241 | original_string: String::new(), | 250 | original_string: String::new(), |
| @@ -550,6 +559,7 @@ mod tests { | |||
| 550 | trusted_maintainer: TEST_KEY_1_KEYS.public_key(), | 559 | trusted_maintainer: TEST_KEY_1_KEYS.public_key(), |
| 551 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | 560 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], |
| 552 | events: HashMap::new(), | 561 | events: HashMap::new(), |
| 562 | nostr_git_url: None, | ||
| 553 | } | 563 | } |
| 554 | .to_event(&TEST_KEY_1_SIGNER) | 564 | .to_event(&TEST_KEY_1_SIGNER) |
| 555 | .await | 565 | .await |