diff options
Diffstat (limited to 'src/repo_ref.rs')
| -rw-r--r-- | src/repo_ref.rs | 171 |
1 files changed, 166 insertions, 5 deletions
diff --git a/src/repo_ref.rs b/src/repo_ref.rs index 8b34d2b..4952b16 100644 --- a/src/repo_ref.rs +++ b/src/repo_ref.rs | |||
| @@ -1,8 +1,16 @@ | |||
| 1 | use std::{fs::File, io::BufReader, str::FromStr}; | 1 | use std::{ |
| 2 | collections::{HashMap, HashSet}, | ||
| 3 | fs::File, | ||
| 4 | io::BufReader, | ||
| 5 | str::FromStr, | ||
| 6 | }; | ||
| 2 | 7 | ||
| 3 | use anyhow::{bail, Context, Result}; | 8 | use anyhow::{bail, Context, Result}; |
| 4 | use nostr::{nips::nip19::Nip19, FromBech32, PublicKey, Tag, TagStandard, ToBech32}; | 9 | use nostr::{ |
| 5 | use nostr_sdk::NostrSigner; | 10 | nips::{nip01::Coordinate, nip19::Nip19}, |
| 11 | FromBech32, PublicKey, Tag, TagStandard, ToBech32, | ||
| 12 | }; | ||
| 13 | use nostr_sdk::{Kind, NostrSigner, Timestamp}; | ||
| 6 | use serde::{Deserialize, Serialize}; | 14 | use serde::{Deserialize, Serialize}; |
| 7 | 15 | ||
| 8 | #[cfg(not(test))] | 16 | #[cfg(not(test))] |
| @@ -11,7 +19,7 @@ use crate::client::Client; | |||
| 11 | use crate::client::MockConnect; | 19 | use crate::client::MockConnect; |
| 12 | use crate::{ | 20 | use crate::{ |
| 13 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, | 21 | cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, |
| 14 | client::{sign_event, Connect}, | 22 | client::{get_event_from_cache, get_event_from_global_cache, sign_event, Connect}, |
| 15 | git::{Repo, RepoActions}, | 23 | git::{Repo, RepoActions}, |
| 16 | }; | 24 | }; |
| 17 | 25 | ||
| @@ -25,6 +33,7 @@ pub struct RepoRef { | |||
| 25 | pub web: Vec<String>, | 33 | pub web: Vec<String>, |
| 26 | pub relays: Vec<String>, | 34 | pub relays: Vec<String>, |
| 27 | pub maintainers: Vec<PublicKey>, | 35 | pub maintainers: Vec<PublicKey>, |
| 36 | pub events: HashMap<Coordinate, nostr::Event>, | ||
| 28 | // code languages and hashtags | 37 | // code languages and hashtags |
| 29 | } | 38 | } |
| 30 | 39 | ||
| @@ -88,7 +97,16 @@ impl TryFrom<nostr::Event> for RepoRef { | |||
| 88 | } else { | 97 | } else { |
| 89 | r.maintainers = vec![event.pubkey]; | 98 | r.maintainers = vec![event.pubkey]; |
| 90 | } | 99 | } |
| 91 | 100 | r.events = HashMap::new(); | |
| 101 | r.events.insert( | ||
| 102 | Coordinate { | ||
| 103 | kind: event.kind, | ||
| 104 | identifier: event.identifier().unwrap().to_string(), | ||
| 105 | public_key: event.author(), | ||
| 106 | relays: vec![], | ||
| 107 | }, | ||
| 108 | event, | ||
| 109 | ); | ||
| 92 | Ok(r) | 110 | Ok(r) |
| 93 | } | 111 | } |
| 94 | } | 112 | } |
| @@ -160,6 +178,145 @@ impl RepoRef { | |||
| 160 | .await | 178 | .await |
| 161 | .context("failed to create repository reference event") | 179 | .context("failed to create repository reference event") |
| 162 | } | 180 | } |
| 181 | pub fn coordinates(&self) -> HashSet<Coordinate> { | ||
| 182 | let mut res = HashSet::new(); | ||
| 183 | for m in &self.maintainers { | ||
| 184 | res.insert(Coordinate { | ||
| 185 | kind: Kind::Custom(REPO_REF_KIND), | ||
| 186 | public_key: *m, | ||
| 187 | identifier: self.identifier.clone(), | ||
| 188 | relays: vec![], | ||
| 189 | }); | ||
| 190 | } | ||
| 191 | res | ||
| 192 | } | ||
| 193 | pub fn coordinates_with_timestamps(&self) -> Vec<(Coordinate, Option<Timestamp>)> { | ||
| 194 | self.coordinates() | ||
| 195 | .iter() | ||
| 196 | .map(|c| (c.clone(), self.events.get(c).map(|e| e.created_at))) | ||
| 197 | .collect::<Vec<(Coordinate, Option<Timestamp>)>>() | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | pub async fn get_repo_coordinates( | ||
| 202 | git_repo: &Repo, | ||
| 203 | #[cfg(test)] client: &crate::client::MockConnect, | ||
| 204 | #[cfg(not(test))] client: &Client, | ||
| 205 | ) -> Result<HashSet<Coordinate>> { | ||
| 206 | let mut repo_coordinates = HashSet::new(); | ||
| 207 | |||
| 208 | if let Some(repo_override) = git_repo.get_git_config_item("nostr.repo", Some(false))? { | ||
| 209 | for s in repo_override.split(',') { | ||
| 210 | if let Ok(c) = Coordinate::parse(s) { | ||
| 211 | repo_coordinates.insert(c); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | // TODO: when nostr remotes functionality is added, iterate on each remote and | ||
| 217 | // extract coordinates | ||
| 218 | |||
| 219 | if repo_coordinates.is_empty() { | ||
| 220 | if let Ok(repo_config) = get_repo_config_from_yaml(git_repo) { | ||
| 221 | let maintainers = { | ||
| 222 | let mut maintainers = HashSet::new(); | ||
| 223 | for m in &repo_config.maintainers { | ||
| 224 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 225 | maintainers.insert(maintainer); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | maintainers | ||
| 229 | }; | ||
| 230 | if let Some(identifier) = repo_config.identifier { | ||
| 231 | for public_key in maintainers { | ||
| 232 | repo_coordinates.insert(Coordinate { | ||
| 233 | kind: Kind::Custom(REPO_REF_KIND), | ||
| 234 | public_key, | ||
| 235 | identifier: identifier.clone(), | ||
| 236 | relays: vec![], | ||
| 237 | }); | ||
| 238 | } | ||
| 239 | } else { | ||
| 240 | // if repo_config.identifier.is_empty() { | ||
| 241 | // this will only apply for a few repositories created before ngit v1.3 | ||
| 242 | // that haven't updated their maintainers.yaml | ||
| 243 | if let Ok(Some(current_user_npub)) = | ||
| 244 | git_repo.get_git_config_item("nostr.npub", None) | ||
| 245 | { | ||
| 246 | if let Ok(current_user) = PublicKey::parse(current_user_npub) { | ||
| 247 | for m in &repo_config.maintainers { | ||
| 248 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 249 | if current_user.eq(&maintainer) { | ||
| 250 | println!( | ||
| 251 | "please run `nigt init` to add the repo identifier to maintainers.yaml" | ||
| 252 | ); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | // look find all repo refs with root_commit. for identifier | ||
| 259 | let filter = nostr::Filter::default() | ||
| 260 | .kind(nostr::Kind::Custom(REPO_REF_KIND)) | ||
| 261 | .reference(git_repo.get_root_commit()?.to_string()) | ||
| 262 | .authors(maintainers.clone()); | ||
| 263 | let mut events = | ||
| 264 | get_event_from_cache(git_repo.get_path()?, vec![filter.clone()]).await?; | ||
| 265 | if events.is_empty() { | ||
| 266 | events = | ||
| 267 | get_event_from_global_cache(git_repo.get_path()?, vec![filter.clone()]) | ||
| 268 | .await?; | ||
| 269 | } | ||
| 270 | if events.is_empty() { | ||
| 271 | events = client | ||
| 272 | .get_events(client.get_fallback_relays().clone(), vec![filter.clone()]) | ||
| 273 | .await?; | ||
| 274 | } | ||
| 275 | if let Some(e) = events.first() { | ||
| 276 | if let Some(identifier) = e.identifier() { | ||
| 277 | for m in &repo_config.maintainers { | ||
| 278 | if let Ok(maintainer) = PublicKey::parse(m) { | ||
| 279 | repo_coordinates.insert(Coordinate { | ||
| 280 | kind: Kind::Custom(REPO_REF_KIND), | ||
| 281 | public_key: maintainer, | ||
| 282 | identifier: identifier.to_string(), | ||
| 283 | relays: vec![], | ||
| 284 | }); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } else { | ||
| 289 | let c = ask_for_naddr()?; | ||
| 290 | git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?; | ||
| 291 | repo_coordinates.insert(c); | ||
| 292 | } | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | if repo_coordinates.is_empty() { | ||
| 298 | // TODO: present list of events filter by root_commit | ||
| 299 | // TODO: fallback to search based on identifier | ||
| 300 | let c = ask_for_naddr()?; | ||
| 301 | // PROBLEM: we are saving this before checking whether it actually exists, which | ||
| 302 | // means next time the user won't be prompted and may not know how to | ||
| 303 | // change the selected repo | ||
| 304 | git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?; | ||
| 305 | repo_coordinates.insert(c); | ||
| 306 | } | ||
| 307 | Ok(repo_coordinates) | ||
| 308 | } | ||
| 309 | |||
| 310 | fn ask_for_naddr() -> Result<Coordinate> { | ||
| 311 | let mut prompt = "repository naddr"; | ||
| 312 | Ok(loop { | ||
| 313 | if let Ok(c) = Coordinate::parse( | ||
| 314 | Interactor::default().input(PromptInputParms::default().with_prompt(prompt))?, | ||
| 315 | ) { | ||
| 316 | break c; | ||
| 317 | } | ||
| 318 | prompt = "repository valid naddr"; | ||
| 319 | }) | ||
| 163 | } | 320 | } |
| 164 | 321 | ||
| 165 | pub async fn fetch( | 322 | pub async fn fetch( |
| @@ -248,6 +405,7 @@ pub async fn fetch( | |||
| 248 | 405 | ||
| 249 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] | 406 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] |
| 250 | pub struct RepoConfigYaml { | 407 | pub struct RepoConfigYaml { |
| 408 | pub identifier: Option<String>, | ||
| 251 | pub maintainers: Vec<String>, | 409 | pub maintainers: Vec<String>, |
| 252 | pub relays: Vec<String>, | 410 | pub relays: Vec<String>, |
| 253 | } | 411 | } |
| @@ -277,6 +435,7 @@ pub fn extract_pks(pk_strings: Vec<String>) -> Result<Vec<PublicKey>> { | |||
| 277 | 435 | ||
| 278 | pub fn save_repo_config_to_yaml( | 436 | pub fn save_repo_config_to_yaml( |
| 279 | git_repo: &Repo, | 437 | git_repo: &Repo, |
| 438 | identifier: String, | ||
| 280 | maintainers: Vec<PublicKey>, | 439 | maintainers: Vec<PublicKey>, |
| 281 | relays: Vec<String>, | 440 | relays: Vec<String>, |
| 282 | ) -> Result<()> { | 441 | ) -> Result<()> { |
| @@ -301,6 +460,7 @@ pub fn save_repo_config_to_yaml( | |||
| 301 | serde_yaml::to_writer( | 460 | serde_yaml::to_writer( |
| 302 | file, | 461 | file, |
| 303 | &RepoConfigYaml { | 462 | &RepoConfigYaml { |
| 463 | identifier: Some(identifier), | ||
| 304 | maintainers: maintainers_npubs, | 464 | maintainers: maintainers_npubs, |
| 305 | relays, | 465 | relays, |
| 306 | }, | 466 | }, |
| @@ -327,6 +487,7 @@ mod tests { | |||
| 327 | ], | 487 | ], |
| 328 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | 488 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], |
| 329 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | 489 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], |
| 490 | events: HashMap::new(), | ||
| 330 | } | 491 | } |
| 331 | .to_event(&TEST_KEY_1_SIGNER) | 492 | .to_event(&TEST_KEY_1_SIGNER) |
| 332 | .await | 493 | .await |