diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-12 00:00:00 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-12 00:00:00 +0000 |
| commit | 3e52ecb609f8424cffb2e6398c599aa78224825a (patch) | |
| tree | 5c199510c10e206d6c6801dbabdae9691c03341c /src/repo_ref.rs | |
| parent | 6d3c9218d2d3320f5d7fb9b9ede8750e947b70e8 (diff) | |
feat(claim) create yaml add maintainers and relays
- create yaml file with maintainers and relays
- add maintainers to repo event
- add current user as maintainer
- custom repo relays from cli argument
- save git-server in repo event
Diffstat (limited to 'src/repo_ref.rs')
| -rw-r--r-- | src/repo_ref.rs | 184 |
1 files changed, 165 insertions, 19 deletions
diff --git a/src/repo_ref.rs b/src/repo_ref.rs index a92b5b3..a5b35c9 100644 --- a/src/repo_ref.rs +++ b/src/repo_ref.rs | |||
| @@ -1,20 +1,26 @@ | |||
| 1 | use std::{fs::File, io::BufReader, str::FromStr}; | ||
| 2 | |||
| 1 | use anyhow::{bail, Context, Result}; | 3 | use anyhow::{bail, Context, Result}; |
| 2 | use nostr::Tag; | 4 | use nostr::{secp256k1::XOnlyPublicKey, FromBech32, Tag, ToBech32}; |
| 5 | use serde::{Deserialize, Serialize}; | ||
| 3 | 6 | ||
| 4 | #[cfg(not(test))] | 7 | #[cfg(not(test))] |
| 5 | use crate::client::Client; | 8 | use crate::client::Client; |
| 6 | use crate::client::Connect; | ||
| 7 | #[cfg(test)] | 9 | #[cfg(test)] |
| 8 | use crate::client::MockConnect; | 10 | use crate::client::MockConnect; |
| 11 | use crate::{ | ||
| 12 | client::Connect, | ||
| 13 | git::{Repo, RepoActions}, | ||
| 14 | }; | ||
| 9 | 15 | ||
| 10 | #[derive(Default)] | 16 | #[derive(Default)] |
| 11 | pub struct RepoRef { | 17 | pub struct RepoRef { |
| 12 | pub name: String, | 18 | pub name: String, |
| 13 | pub description: String, | 19 | pub description: String, |
| 14 | pub root_commit: String, | 20 | pub root_commit: String, |
| 21 | pub git_server: String, | ||
| 15 | pub relays: Vec<String>, | 22 | pub relays: Vec<String>, |
| 16 | // git_server: String, | 23 | pub maintainers: Vec<XOnlyPublicKey>, |
| 17 | // other maintainers | ||
| 18 | // code languages and hashtags | 24 | // code languages and hashtags |
| 19 | } | 25 | } |
| 20 | 26 | ||
| @@ -35,6 +41,10 @@ impl TryFrom<nostr::Event> for RepoRef { | |||
| 35 | r.description = t.as_vec()[1].clone(); | 41 | r.description = t.as_vec()[1].clone(); |
| 36 | } | 42 | } |
| 37 | 43 | ||
| 44 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("git-server")) { | ||
| 45 | r.git_server = t.as_vec()[1].clone(); | ||
| 46 | } | ||
| 47 | |||
| 38 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { | 48 | if let Some(t) = event.tags.iter().find(|t| t.as_vec()[0].eq("d")) { |
| 39 | r.root_commit = t.as_vec()[1].clone(); | 49 | r.root_commit = t.as_vec()[1].clone(); |
| 40 | } | 50 | } |
| @@ -46,6 +56,15 @@ impl TryFrom<nostr::Event> for RepoRef { | |||
| 46 | .map(|t| t.as_vec()[1].clone()) | 56 | .map(|t| t.as_vec()[1].clone()) |
| 47 | .collect(); | 57 | .collect(); |
| 48 | 58 | ||
| 59 | for tag in event.tags.iter().filter(|t| t.as_vec()[0].eq("p")) { | ||
| 60 | let pk = tag.as_vec()[1].clone(); | ||
| 61 | r.maintainers.push( | ||
| 62 | nostr_sdk::prelude::XOnlyPublicKey::from_str(&pk) | ||
| 63 | .context(format!("cannot convert {pk} into a valid nostr public key")) | ||
| 64 | .context("invalid repository event")?, | ||
| 65 | ); | ||
| 66 | } | ||
| 67 | |||
| 49 | Ok(r) | 68 | Ok(r) |
| 50 | } | 69 | } |
| 51 | } | 70 | } |
| @@ -62,10 +81,17 @@ impl RepoRef { | |||
| 62 | Tag::Reference(format!("r-{}", self.root_commit)), | 81 | Tag::Reference(format!("r-{}", self.root_commit)), |
| 63 | Tag::Name(self.name.clone()), | 82 | Tag::Name(self.name.clone()), |
| 64 | Tag::Description(self.description.clone()), | 83 | Tag::Description(self.description.clone()), |
| 84 | Tag::Generic( | ||
| 85 | nostr::TagKind::Custom("git-server".to_string()), | ||
| 86 | vec![self.git_server.clone()], | ||
| 87 | ), | ||
| 88 | Tag::Reference(self.git_server.clone()), | ||
| 65 | ], | 89 | ], |
| 66 | self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), | 90 | self.relays.iter().map(|r| Tag::Relay(r.into())).collect(), |
| 67 | // git_servers | 91 | self.maintainers |
| 68 | // other maintainers | 92 | .iter() |
| 93 | .map(|pk| Tag::PubKey(*pk, None)) | ||
| 94 | .collect(), | ||
| 69 | // code languages and hashtags | 95 | // code languages and hashtags |
| 70 | ] | 96 | ] |
| 71 | .concat(), | 97 | .concat(), |
| @@ -76,24 +102,30 @@ impl RepoRef { | |||
| 76 | } | 102 | } |
| 77 | 103 | ||
| 78 | pub async fn fetch( | 104 | pub async fn fetch( |
| 105 | git_repo: &Repo, | ||
| 79 | root_commit: String, | 106 | root_commit: String, |
| 80 | #[cfg(test)] client: &MockConnect, | 107 | #[cfg(test)] client: &MockConnect, |
| 81 | #[cfg(not(test))] client: &Client, | 108 | #[cfg(not(test))] client: &Client, |
| 82 | // TODO: more rubust way of finding repo events | 109 | // TODO: more rubust way of finding repo events |
| 83 | relays: Vec<String>, | 110 | fallback_relays: Vec<String>, |
| 84 | ) -> Result<RepoRef> { | 111 | ) -> Result<RepoRef> { |
| 85 | // TODO: fetch relay information from file | 112 | let repo_config = get_repo_config_from_yaml(git_repo); |
| 86 | 113 | ||
| 87 | let events: Vec<nostr::Event> = client | 114 | // TODO: check events only from maintainers. get relay list of maintainters. |
| 88 | .get_events( | 115 | // check those relays. |
| 89 | relays, | 116 | |
| 90 | vec![ | 117 | let mut repo_event_filter = nostr::Filter::default() |
| 91 | nostr::Filter::default() | 118 | .kind(nostr::Kind::Custom(REPO_REF_KIND)) |
| 92 | .kind(nostr::Kind::Custom(REPO_REF_KIND)) | 119 | .identifier(root_commit); |
| 93 | .identifier(root_commit), | 120 | |
| 94 | ], | 121 | let mut relays = fallback_relays; |
| 95 | ) | 122 | if let Ok(repo_config) = repo_config { |
| 96 | .await?; | 123 | repo_event_filter = |
| 124 | repo_event_filter.pubkeys(extract_pks(repo_config.maintainers.clone())?); | ||
| 125 | relays = repo_config.relays.clone(); | ||
| 126 | } | ||
| 127 | |||
| 128 | let events: Vec<nostr::Event> = client.get_events(relays, vec![repo_event_filter]).await?; | ||
| 97 | 129 | ||
| 98 | RepoRef::try_from( | 130 | RepoRef::try_from( |
| 99 | events | 131 | events |
| @@ -105,6 +137,68 @@ pub async fn fetch( | |||
| 105 | ) | 137 | ) |
| 106 | } | 138 | } |
| 107 | 139 | ||
| 140 | #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] | ||
| 141 | pub struct RepoConfigYaml { | ||
| 142 | pub maintainers: Vec<String>, | ||
| 143 | pub relays: Vec<String>, | ||
| 144 | } | ||
| 145 | |||
| 146 | pub fn get_repo_config_from_yaml(git_repo: &Repo) -> Result<RepoConfigYaml> { | ||
| 147 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 148 | let file = File::open(path) | ||
| 149 | .context("should open maintainers.yaml if it exists") | ||
| 150 | .context("maintainers.yaml doesnt exist")?; | ||
| 151 | let reader = BufReader::new(file); | ||
| 152 | let repo_config_yaml: RepoConfigYaml = serde_yaml::from_reader(reader) | ||
| 153 | .context("should read maintainers.yaml with serde_yaml") | ||
| 154 | .context("maintainers.yaml incorrectly formatted")?; | ||
| 155 | Ok(repo_config_yaml) | ||
| 156 | } | ||
| 157 | |||
| 158 | pub fn extract_pks(pk_strings: Vec<String>) -> Result<Vec<XOnlyPublicKey>> { | ||
| 159 | let mut pks: Vec<XOnlyPublicKey> = vec![]; | ||
| 160 | for s in pk_strings { | ||
| 161 | pks.push( | ||
| 162 | nostr_sdk::prelude::XOnlyPublicKey::from_bech32(s.clone()) | ||
| 163 | .context(format!("cannot convert {s} into a valid nostr public key"))?, | ||
| 164 | ); | ||
| 165 | } | ||
| 166 | Ok(pks) | ||
| 167 | } | ||
| 168 | |||
| 169 | pub fn save_repo_config_to_yaml( | ||
| 170 | git_repo: &Repo, | ||
| 171 | maintainers: Vec<XOnlyPublicKey>, | ||
| 172 | relays: Vec<String>, | ||
| 173 | ) -> Result<()> { | ||
| 174 | let path = git_repo.get_path()?.join("maintainers.yaml"); | ||
| 175 | let file = if path.exists() { | ||
| 176 | std::fs::OpenOptions::new() | ||
| 177 | .create(true) | ||
| 178 | .write(true) | ||
| 179 | .truncate(true) | ||
| 180 | .open(path) | ||
| 181 | .context("cannot open maintainers.yaml file with write and truncate options")? | ||
| 182 | } else { | ||
| 183 | std::fs::File::create(path).context("cannot create maintainers.yaml file")? | ||
| 184 | }; | ||
| 185 | let mut maintainers_npubs = vec![]; | ||
| 186 | for m in maintainers { | ||
| 187 | maintainers_npubs.push( | ||
| 188 | m.to_bech32() | ||
| 189 | .context("cannot convert public key into npub")?, | ||
| 190 | ); | ||
| 191 | } | ||
| 192 | serde_yaml::to_writer( | ||
| 193 | file, | ||
| 194 | &RepoConfigYaml { | ||
| 195 | maintainers: maintainers_npubs, | ||
| 196 | relays, | ||
| 197 | }, | ||
| 198 | ) | ||
| 199 | .context("cannot write maintainers to maintainers.yaml file serde_yaml") | ||
| 200 | } | ||
| 201 | |||
| 108 | #[cfg(test)] | 202 | #[cfg(test)] |
| 109 | mod tests { | 203 | mod tests { |
| 110 | use test_utils::*; | 204 | use test_utils::*; |
| @@ -116,7 +210,9 @@ mod tests { | |||
| 116 | name: "test name".to_string(), | 210 | name: "test name".to_string(), |
| 117 | description: "test description".to_string(), | 211 | description: "test description".to_string(), |
| 118 | root_commit: "23471389461".to_string(), | 212 | root_commit: "23471389461".to_string(), |
| 213 | git_server: "https://localhost:1000".to_string(), | ||
| 119 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | 214 | relays: vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], |
| 215 | maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 120 | } | 216 | } |
| 121 | .to_event(&TEST_KEY_1_KEYS) | 217 | .to_event(&TEST_KEY_1_KEYS) |
| 122 | .unwrap() | 218 | .unwrap() |
| @@ -146,12 +242,28 @@ mod tests { | |||
| 146 | } | 242 | } |
| 147 | 243 | ||
| 148 | #[test] | 244 | #[test] |
| 245 | fn git_server() { | ||
| 246 | assert_eq!( | ||
| 247 | RepoRef::try_from(create()).unwrap().git_server, | ||
| 248 | "https://localhost:1000", | ||
| 249 | ) | ||
| 250 | } | ||
| 251 | |||
| 252 | #[test] | ||
| 149 | fn relays() { | 253 | fn relays() { |
| 150 | assert_eq!( | 254 | assert_eq!( |
| 151 | RepoRef::try_from(create()).unwrap().relays, | 255 | RepoRef::try_from(create()).unwrap().relays, |
| 152 | vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], | 256 | vec!["ws://relay1.io".to_string(), "ws://relay2.io".to_string()], |
| 153 | ) | 257 | ) |
| 154 | } | 258 | } |
| 259 | |||
| 260 | #[test] | ||
| 261 | fn maintainers() { | ||
| 262 | assert_eq!( | ||
| 263 | RepoRef::try_from(create()).unwrap().maintainers, | ||
| 264 | vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], | ||
| 265 | ) | ||
| 266 | } | ||
| 155 | } | 267 | } |
| 156 | 268 | ||
| 157 | mod to_event { | 269 | mod to_event { |
| @@ -186,6 +298,21 @@ mod tests { | |||
| 186 | } | 298 | } |
| 187 | 299 | ||
| 188 | #[test] | 300 | #[test] |
| 301 | fn git_server() { | ||
| 302 | assert!(create().tags.iter().any(|t| t.as_vec()[0].eq("git-server") | ||
| 303 | && t.as_vec()[1].eq("https://localhost:1000"))) | ||
| 304 | } | ||
| 305 | |||
| 306 | #[test] | ||
| 307 | fn git_server_as_reference() { | ||
| 308 | assert!( | ||
| 309 | create().tags.iter().any( | ||
| 310 | |t| t.as_vec()[0].eq("r") && t.as_vec()[1].eq("https://localhost:1000") | ||
| 311 | ) | ||
| 312 | ) | ||
| 313 | } | ||
| 314 | |||
| 315 | #[test] | ||
| 189 | fn root_commit_as_reference() { | 316 | fn root_commit_as_reference() { |
| 190 | assert!( | 317 | assert!( |
| 191 | create() | 318 | create() |
| @@ -209,8 +336,27 @@ mod tests { | |||
| 209 | } | 336 | } |
| 210 | 337 | ||
| 211 | #[test] | 338 | #[test] |
| 339 | fn maintainers() { | ||
| 340 | let event = create(); | ||
| 341 | let p_tags = event | ||
| 342 | .tags | ||
| 343 | .iter() | ||
| 344 | .filter(|t| t.as_vec()[0].eq("p")) | ||
| 345 | .collect::<Vec<&nostr::Tag>>(); | ||
| 346 | assert_eq!(p_tags[0].as_vec().len(), 2); | ||
| 347 | assert_eq!( | ||
| 348 | p_tags[0].as_vec()[1], | ||
| 349 | TEST_KEY_1_KEYS.public_key().to_string() | ||
| 350 | ); | ||
| 351 | assert_eq!( | ||
| 352 | p_tags[1].as_vec()[1], | ||
| 353 | TEST_KEY_2_KEYS.public_key().to_string() | ||
| 354 | ); | ||
| 355 | } | ||
| 356 | |||
| 357 | #[test] | ||
| 212 | fn no_other_tags() { | 358 | fn no_other_tags() { |
| 213 | assert_eq!(create().tags.len(), 6) | 359 | assert_eq!(create().tags.len(), 10) |
| 214 | } | 360 | } |
| 215 | } | 361 | } |
| 216 | } | 362 | } |