upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/repo_ref.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/repo_ref.rs')
-rw-r--r--src/repo_ref.rs184
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 @@
1use std::{fs::File, io::BufReader, str::FromStr};
2
1use anyhow::{bail, Context, Result}; 3use anyhow::{bail, Context, Result};
2use nostr::Tag; 4use nostr::{secp256k1::XOnlyPublicKey, FromBech32, Tag, ToBech32};
5use serde::{Deserialize, Serialize};
3 6
4#[cfg(not(test))] 7#[cfg(not(test))]
5use crate::client::Client; 8use crate::client::Client;
6use crate::client::Connect;
7#[cfg(test)] 9#[cfg(test)]
8use crate::client::MockConnect; 10use crate::client::MockConnect;
11use crate::{
12 client::Connect,
13 git::{Repo, RepoActions},
14};
9 15
10#[derive(Default)] 16#[derive(Default)]
11pub struct RepoRef { 17pub 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
78pub async fn fetch( 104pub 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)]
141pub struct RepoConfigYaml {
142 pub maintainers: Vec<String>,
143 pub relays: Vec<String>,
144}
145
146pub 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
158pub 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
169pub 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)]
109mod tests { 203mod 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 }