upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/cli_interactor.rs87
-rw-r--r--src/lib/client.rs54
-rw-r--r--src/lib/git_events.rs1
-rw-r--r--src/lib/login/user.rs77
-rw-r--r--src/lib/repo_ref.rs83
5 files changed, 267 insertions, 35 deletions
diff --git a/src/lib/cli_interactor.rs b/src/lib/cli_interactor.rs
index 8fca81d..8bcda19 100644
--- a/src/lib/cli_interactor.rs
+++ b/src/lib/cli_interactor.rs
@@ -236,6 +236,93 @@ impl PromptMultiChoiceParms {
236 } 236 }
237} 237}
238 238
239pub fn multi_select_with_custom_value<F>(
240 prompt: &str,
241 custom_choice_prompt: &str,
242 mut choices: Vec<String>,
243 mut defaults: Vec<bool>,
244 validate_choice: F,
245) -> Result<Vec<String>>
246where
247 F: Fn(&str) -> Result<String>,
248{
249 let mut selected_choices = vec![];
250
251 // Loop to allow users to add more choices
252 loop {
253 // Add 'add another' option at the end of the choices
254 let mut current_choices = choices.clone();
255 current_choices.push(if current_choices.is_empty() {
256 "add".to_string()
257 } else {
258 "add another".to_string()
259 });
260
261 // Create default selections based on the provided defaults
262 let mut current_defaults = defaults.clone();
263 current_defaults.push(current_choices.len() == 1); // 'add another' should not be selected by default
264
265 // Prompt for selections
266 let selected_indices: Vec<usize> = Interactor::default().multi_choice(
267 PromptMultiChoiceParms::default()
268 .with_prompt(prompt)
269 .dont_report()
270 .with_choices(current_choices.clone())
271 .with_defaults(current_defaults),
272 )?;
273
274 // Collect selected choices
275 selected_choices.clear(); // Clear previous selections to update
276 for &index in &selected_indices {
277 if index < choices.len() {
278 // Exclude 'add another' option
279 selected_choices.push(choices[index].clone());
280 }
281 }
282
283 // Check if 'add another' was selected
284 if selected_indices.contains(&(choices.len())) {
285 // Last index is 'add another'
286 let mut new_choice: String;
287 loop {
288 new_choice = Interactor::default().input(
289 PromptInputParms::default()
290 .with_prompt(custom_choice_prompt)
291 .dont_report()
292 .optional(),
293 )?;
294
295 if new_choice.is_empty() {
296 break;
297 }
298 // Validate the new choice
299 match validate_choice(&new_choice) {
300 Ok(valid_choice) => {
301 new_choice = valid_choice; // Use the fixed version of the input
302 break; // Valid choice, exit the loop
303 }
304 Err(err) => {
305 // Inform the user about the validation error
306 println!("Error: {err}");
307 }
308 }
309 }
310
311 // Add the new choice to the choices vector
312 if !new_choice.is_empty() {
313 choices.push(new_choice.clone()); // Add new choice to the end of the list
314 selected_choices.push(new_choice); // Automatically select the new choice
315 defaults.push(true); // Set the new choice as selected by default
316 }
317 } else {
318 // Exit the loop if 'add another' was not selected
319 break;
320 }
321 }
322
323 Ok(selected_choices)
324}
325
239#[derive(Debug, Default)] 326#[derive(Debug, Default)]
240pub struct Printer { 327pub struct Printer {
241 printed_lines: Vec<String>, 328 printed_lines: Vec<String>,
diff --git a/src/lib/client.rs b/src/lib/client.rs
index b27f9b1..9ce3e24 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -53,7 +53,7 @@ use crate::{
53 get_dirs, 53 get_dirs,
54 git::{Repo, RepoActions, get_git_config_item}, 54 git::{Repo, RepoActions, get_git_config_item},
55 git_events::{ 55 git_events::{
56 KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter, 56 KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, KIND_USER_GRASP_LIST, event_is_cover_letter,
57 event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, 57 event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update,
58 status_kinds, 58 status_kinds,
59 }, 59 },
@@ -233,7 +233,7 @@ impl Connect for Client {
233 if let Some(git_repo_path) = git_repo_path { 233 if let Some(git_repo_path) = git_repo_path {
234 save_event_in_local_cache(git_repo_path, &event).await?; 234 save_event_in_local_cache(git_repo_path, &event).await?;
235 } 235 }
236 if event.kind.eq(&Kind::GitRepoAnnouncement) { 236 if [Kind::GitRepoAnnouncement, KIND_USER_GRASP_LIST].contains(&event.kind) {
237 save_event_in_global_cache(git_repo_path, &event).await?; 237 save_event_in_global_cache(git_repo_path, &event).await?;
238 } 238 }
239 Ok(event.id) 239 Ok(event.id)
@@ -1310,17 +1310,21 @@ async fn create_relays_request(
1310 user_profiles.insert(current_user); 1310 user_profiles.insert(current_user);
1311 } 1311 }
1312 } 1312 }
1313 let mut map: HashMap<PublicKey, (Timestamp, Timestamp)> = HashMap::new(); 1313 let mut map: HashMap<PublicKey, (Timestamp, Timestamp, Timestamp)> = HashMap::new();
1314 for public_key in &user_profiles { 1314 for public_key in &user_profiles {
1315 if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await { 1315 if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await {
1316 map.insert( 1316 map.insert(
1317 public_key.to_owned(), 1317 public_key.to_owned(),
1318 (user_ref.metadata.created_at, user_ref.relays.created_at), 1318 (
1319 user_ref.metadata.created_at,
1320 user_ref.relays.created_at,
1321 user_ref.grasp_list.created_at,
1322 ),
1319 ); 1323 );
1320 } else { 1324 } else {
1321 map.insert( 1325 map.insert(
1322 public_key.to_owned(), 1326 public_key.to_owned(),
1323 (Timestamp::from(0), Timestamp::from(0)), 1327 (Timestamp::from(0), Timestamp::from(0), Timestamp::from(0)),
1324 ); 1328 );
1325 } 1329 }
1326 } 1330 }
@@ -1547,16 +1551,22 @@ async fn process_fetched_events(
1547 { 1551 {
1548 fresh_profiles.insert(event.pubkey); 1552 fresh_profiles.insert(event.pubkey);
1549 } 1553 }
1550 } else if [Kind::RelayList, Kind::Metadata].contains(&event.kind) { 1554 } else if [Kind::RelayList, Kind::Metadata, KIND_USER_GRASP_LIST].contains(&event.kind)
1555 {
1551 if request.missing_contributor_profiles.contains(&event.pubkey) { 1556 if request.missing_contributor_profiles.contains(&event.pubkey) {
1552 report.contributor_profiles.insert(event.pubkey); 1557 report.contributor_profiles.insert(event.pubkey);
1553 } else if let Some((_, (metadata_timestamp, relay_list_timestamp))) = request 1558 } else if let Some((
1559 _,
1560 (metadata_timestamp, relay_list_timestamp, grasp_list_timestamp),
1561 )) = request
1554 .profiles_to_fetch_from_user_relays 1562 .profiles_to_fetch_from_user_relays
1555 .get_key_value(&event.pubkey) 1563 .get_key_value(&event.pubkey)
1556 { 1564 {
1557 if (Kind::Metadata.eq(&event.kind) && event.created_at.gt(metadata_timestamp)) 1565 if (Kind::Metadata.eq(&event.kind) && event.created_at.gt(metadata_timestamp))
1558 || (Kind::RelayList.eq(&event.kind) 1566 || (Kind::RelayList.eq(&event.kind)
1559 && event.created_at.gt(relay_list_timestamp)) 1567 && event.created_at.gt(relay_list_timestamp))
1568 || (KIND_USER_GRASP_LIST.eq(&event.kind)
1569 && event.created_at.gt(grasp_list_timestamp))
1560 { 1570 {
1561 report.profile_updates.insert(event.pubkey); 1571 report.profile_updates.insert(event.pubkey);
1562 } 1572 }
@@ -1718,35 +1728,21 @@ pub fn get_filter_repo_events(repo_coordinates: &HashSet<Nip19Coordinate>) -> no
1718 .map(|c| c.identifier.clone()) 1728 .map(|c| c.identifier.clone())
1719 .collect::<Vec<String>>(), 1729 .collect::<Vec<String>>(),
1720 ) 1730 )
1721 .authors(
1722 repo_coordinates
1723 .iter()
1724 .map(|c| c.public_key)
1725 .collect::<Vec<PublicKey>>(),
1726 )
1727} 1731}
1728 1732
1729pub static STATE_KIND: nostr::Kind = Kind::Custom(30618); 1733pub static STATE_KIND: nostr::Kind = Kind::Custom(30618);
1730pub fn get_filter_state_events(repo_coordinates: &HashSet<Nip19Coordinate>) -> nostr::Filter { 1734pub fn get_filter_state_events(repo_coordinates: &HashSet<Nip19Coordinate>) -> nostr::Filter {
1731 nostr::Filter::default() 1735 nostr::Filter::default().kind(STATE_KIND).identifiers(
1732 .kind(STATE_KIND) 1736 repo_coordinates
1733 .identifiers( 1737 .iter()
1734 repo_coordinates 1738 .map(|c| c.identifier.clone())
1735 .iter() 1739 .collect::<Vec<String>>(),
1736 .map(|c| c.identifier.clone()) 1740 )
1737 .collect::<Vec<String>>(),
1738 )
1739 .authors(
1740 repo_coordinates
1741 .iter()
1742 .map(|c| c.public_key)
1743 .collect::<Vec<PublicKey>>(),
1744 )
1745} 1741}
1746 1742
1747pub fn get_filter_contributor_profiles(contributors: HashSet<PublicKey>) -> nostr::Filter { 1743pub fn get_filter_contributor_profiles(contributors: HashSet<PublicKey>) -> nostr::Filter {
1748 nostr::Filter::default() 1744 nostr::Filter::default()
1749 .kinds(vec![Kind::Metadata, Kind::RelayList]) 1745 .kinds(vec![Kind::Metadata, Kind::RelayList, KIND_USER_GRASP_LIST])
1750 .authors(contributors) 1746 .authors(contributors)
1751} 1747}
1752 1748
@@ -1850,7 +1846,7 @@ pub struct FetchRequest {
1850 contributors: HashSet<PublicKey>, 1846 contributors: HashSet<PublicKey>,
1851 missing_contributor_profiles: HashSet<PublicKey>, 1847 missing_contributor_profiles: HashSet<PublicKey>,
1852 existing_events: HashSet<EventId>, 1848 existing_events: HashSet<EventId>,
1853 profiles_to_fetch_from_user_relays: HashMap<PublicKey, (Timestamp, Timestamp)>, 1849 profiles_to_fetch_from_user_relays: HashMap<PublicKey, (Timestamp, Timestamp, Timestamp)>,
1854 user_relays_for_profiles: HashSet<RelayUrl>, 1850 user_relays_for_profiles: HashSet<RelayUrl>,
1855} 1851}
1856 1852
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs
index bbfcbea..76c31de 100644
--- a/src/lib/git_events.rs
+++ b/src/lib/git_events.rs
@@ -63,6 +63,7 @@ pub fn status_kinds() -> Vec<Kind> {
63 63
64pub const KIND_PULL_REQUEST: Kind = Kind::Custom(1618); 64pub const KIND_PULL_REQUEST: Kind = Kind::Custom(1618);
65pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619); 65pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619);
66pub const KIND_USER_GRASP_LIST: Kind = Kind::Custom(10317);
66 67
67pub fn event_is_patch_set_root(event: &Event) -> bool { 68pub fn event_is_patch_set_root(event: &Event) -> bool {
68 event.kind.eq(&Kind::GitPatch) 69 event.kind.eq(&Kind::GitPatch)
diff --git a/src/lib/login/user.rs b/src/lib/login/user.rs
index 071cb25..0b702ef 100644
--- a/src/lib/login/user.rs
+++ b/src/lib/login/user.rs
@@ -1,7 +1,7 @@
1use std::{collections::HashSet, path::Path}; 1use std::{collections::HashSet, path::Path, sync::Arc};
2 2
3use anyhow::{Context, Result, bail}; 3use anyhow::{Context, Result, bail};
4use nostr::PublicKey; 4use nostr::{PublicKey, Url, event::Tag, signer::NostrSigner};
5use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32}; 5use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32};
6use serde::{self, Deserialize, Serialize}; 6use serde::{self, Deserialize, Serialize};
7 7
@@ -9,13 +9,17 @@ use serde::{self, Deserialize, Serialize};
9use crate::client::Client; 9use crate::client::Client;
10#[cfg(test)] 10#[cfg(test)]
11use crate::client::MockConnect; 11use crate::client::MockConnect;
12use crate::client::{Connect, get_event_from_global_cache}; 12use crate::{
13 client::{Connect, get_event_from_global_cache, sign_event},
14 git_events::KIND_USER_GRASP_LIST,
15};
13 16
14#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 17#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
15pub struct UserRef { 18pub struct UserRef {
16 pub public_key: PublicKey, 19 pub public_key: PublicKey,
17 pub metadata: UserMetadata, 20 pub metadata: UserMetadata,
18 pub relays: UserRelays, 21 pub relays: UserRelays,
22 pub grasp_list: UserGraspList,
19} 23}
20 24
21#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 25#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
@@ -49,6 +53,35 @@ impl UserRelays {
49} 53}
50 54
51#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 55#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
56pub struct UserGraspList {
57 pub urls: Vec<Url>,
58 pub created_at: Timestamp,
59}
60
61impl UserGraspList {
62 pub async fn to_event(&mut self, signer: &Arc<dyn NostrSigner>) -> Result<nostr::Event> {
63 let event = sign_event(
64 nostr_sdk::EventBuilder::new(KIND_USER_GRASP_LIST, "").tags(
65 self.urls
66 .iter()
67 .map(|url| {
68 Tag::custom(
69 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("g")),
70 vec![url.to_string()],
71 )
72 })
73 .collect::<Vec<_>>(),
74 ),
75 signer,
76 "user grasp list".to_string(),
77 )
78 .await?;
79 self.created_at = event.created_at;
80 Ok(event)
81 }
82}
83
84#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
52pub struct UserRelayRef { 85pub struct UserRelayRef {
53 pub url: String, 86 pub url: String,
54 pub read: bool, 87 pub read: bool,
@@ -84,6 +117,7 @@ pub async fn get_user_details(
84 public_key: public_key.to_owned(), 117 public_key: public_key.to_owned(),
85 metadata: extract_user_metadata(public_key, &[])?, 118 metadata: extract_user_metadata(public_key, &[])?,
86 relays: extract_user_relays(public_key, &[]), 119 relays: extract_user_relays(public_key, &[]),
120 grasp_list: extract_user_grasp_list(public_key, &[]),
87 }; 121 };
88 if cache_only { 122 if cache_only {
89 Ok(empty) 123 Ok(empty)
@@ -117,6 +151,9 @@ pub async fn get_user_ref_from_cache(
117 nostr::Filter::default() 151 nostr::Filter::default()
118 .author(*public_key) 152 .author(*public_key)
119 .kind(Kind::RelayList), 153 .kind(Kind::RelayList),
154 nostr::Filter::default()
155 .author(*public_key)
156 .kind(KIND_USER_GRASP_LIST),
120 ]; 157 ];
121 158
122 let events = get_event_from_global_cache(git_repo_path, filters.clone()).await?; 159 let events = get_event_from_global_cache(git_repo_path, filters.clone()).await?;
@@ -128,6 +165,7 @@ pub async fn get_user_ref_from_cache(
128 public_key: public_key.to_owned(), 165 public_key: public_key.to_owned(),
129 metadata: extract_user_metadata(public_key, &events)?, 166 metadata: extract_user_metadata(public_key, &events)?,
130 relays: extract_user_relays(public_key, &events), 167 relays: extract_user_relays(public_key, &events),
168 grasp_list: extract_user_grasp_list(public_key, &events),
131 }) 169 })
132} 170}
133 171
@@ -215,3 +253,36 @@ pub fn extract_user_relays(public_key: &nostr::PublicKey, events: &[nostr::Event
215 }, 253 },
216 } 254 }
217} 255}
256
257pub fn extract_user_grasp_list(
258 public_key: &nostr::PublicKey,
259 events: &[nostr::Event],
260) -> UserGraspList {
261 let event = events
262 .iter()
263 .filter(|e| e.kind.eq(&KIND_USER_GRASP_LIST) && e.pubkey.eq(public_key))
264 .max_by_key(|e| e.created_at);
265
266 UserGraspList {
267 urls: if let Some(event) = event {
268 event
269 .tags
270 .iter()
271 .filter_map(|t| {
272 if t.as_slice().len() > 1 && t.as_slice()[0] == "g" {
273 Url::parse(&t.as_slice()[1]).ok()
274 } else {
275 None
276 }
277 })
278 .collect()
279 } else {
280 vec![]
281 },
282 created_at: if let Some(event) = event {
283 event.created_at
284 } else {
285 Timestamp::from(0)
286 },
287 }
288}
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs
index a3e1317..e3f71a1 100644
--- a/src/lib/repo_ref.rs
+++ b/src/lib/repo_ref.rs
@@ -40,6 +40,7 @@ pub struct RepoRef {
40 pub web: Vec<String>, 40 pub web: Vec<String>,
41 pub relays: Vec<RelayUrl>, 41 pub relays: Vec<RelayUrl>,
42 pub blossoms: Vec<Url>, 42 pub blossoms: Vec<Url>,
43 pub hashtags: Vec<String>,
43 pub maintainers: Vec<PublicKey>, 44 pub maintainers: Vec<PublicKey>,
44 pub trusted_maintainer: PublicKey, 45 pub trusted_maintainer: PublicKey,
45 // set to None if not known 46 // set to None if not known
@@ -71,6 +72,7 @@ impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef {
71 web: Vec::new(), 72 web: Vec::new(),
72 relays: Vec::new(), 73 relays: Vec::new(),
73 blossoms: Vec::new(), 74 blossoms: Vec::new(),
75 hashtags: Vec::new(),
74 maintainers: Vec::new(), 76 maintainers: Vec::new(),
75 trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey), 77 trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey),
76 maintainers_without_annoucnement: None, 78 maintainers_without_annoucnement: None,
@@ -118,6 +120,7 @@ impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef {
118 } 120 }
119 } 121 }
120 } 122 }
123 [t, hashtag, ..] if t == "t" => r.hashtags.push(hashtag.clone()),
121 [t, blossoms @ ..] if t == "blossoms" => { 124 [t, blossoms @ ..] if t == "blossoms" => {
122 for b in blossoms { 125 for b in blossoms {
123 if let Ok(b) = Url::parse(b) { 126 if let Ok(b) = Url::parse(b) {
@@ -217,6 +220,15 @@ impl RepoRef {
217 vec![format!("git repository: {}", self.name.clone())], 220 vec![format!("git repository: {}", self.name.clone())],
218 ), 221 ),
219 ], 222 ],
223 self.hashtags
224 .iter()
225 .map(|h| {
226 Tag::custom(
227 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("t")),
228 vec![h.clone()],
229 )
230 })
231 .collect(),
220 if self.blossoms.is_empty() { 232 if self.blossoms.is_empty() {
221 vec![] 233 vec![]
222 } else { 234 } else {
@@ -311,6 +323,34 @@ impl RepoRef {
311 pub fn grasp_servers(&self) -> Vec<String> { 323 pub fn grasp_servers(&self) -> Vec<String> {
312 detect_existing_grasp_servers(Some(self), &[], &[], &self.identifier) 324 detect_existing_grasp_servers(Some(self), &[], &[], &self.identifier)
313 } 325 }
326
327 // returns false if already present so didn't need adding
328 pub fn add_grasp_server(&mut self, clone_url: &str) -> Result<bool> {
329 if !clone_url.starts_with("http") {
330 bail!("invalid grasp server clone url");
331 }
332 extract_npub(clone_url)
333 .context("invalid grasp server clone url. does not contain valid npub")?;
334 if !(clone_url.ends_with(".git") || clone_url.ends_with(".git/")) {
335 bail!("invalid grasp server clone url. does not end with .git");
336 }
337
338 let relay_url = RelayUrl::parse(
339 &format_grasp_server_url_as_relay_url(clone_url)
340 .context("invalid grasp server clone url")?,
341 )
342 .context("invalid grasp server clone url")?;
343
344 if !self.relays.contains(&relay_url) {
345 self.relays.push(relay_url);
346 }
347 if !self.git_server.contains(&clone_url.to_string()) {
348 self.git_server.push(clone_url.to_string());
349 Ok(true)
350 } else {
351 Ok(false)
352 }
353 }
314} 354}
315 355
316pub async fn get_repo_coordinates_when_remote_unknown( 356pub async fn get_repo_coordinates_when_remote_unknown(
@@ -699,13 +739,49 @@ pub fn extract_npub(s: &str) -> Result<&str> {
699 } 739 }
700} 740}
701 741
742// this should be called is_grasp_server_in_list
702pub fn is_grasp_server(url: &str, grasp_servers: &[String]) -> bool { 743pub fn is_grasp_server(url: &str, grasp_servers: &[String]) -> bool {
703 if !grasp_servers.is_empty() { 744 if !grasp_servers.is_empty() {
704 if let Ok(n) = normalize_grasp_server_url(url) { 745 if let Ok(url) = normalize_grasp_server_url(url) {
705 return grasp_servers.contains(&n); 746 grasp_servers.iter().any(|s| {
747 if let Ok(s) = normalize_grasp_server_url(s) {
748 s == url
749 } else {
750 false
751 }
752 })
753 } else {
754 false
706 } 755 }
756 } else {
757 false
758 }
759}
760
761pub fn format_grasp_server_url_as_relay_url(url: &str) -> Result<String> {
762 let grasp_server_url = normalize_grasp_server_url(url)?;
763 if grasp_server_url.contains("http://") {
764 return Ok(grasp_server_url.replace("http://", "ws://"));
707 } 765 }
708 false 766 Ok(format!("wss://{grasp_server_url}"))
767}
768
769pub fn format_grasp_server_url_as_clone_url(
770 grasp_server: &str,
771 public_key: &PublicKey,
772 identifier: &str,
773) -> Result<String> {
774 let grasp_server_url = normalize_grasp_server_url(grasp_server)?;
775
776 let prefix = if grasp_server_url.contains("http://") {
777 ""
778 } else {
779 "https://"
780 };
781 Ok(format!(
782 "{prefix}{grasp_server_url}/{}/{identifier}.git",
783 public_key.to_bech32()?
784 ))
709} 785}
710 786
711#[cfg(test)] 787#[cfg(test)]
@@ -730,6 +806,7 @@ mod tests {
730 RelayUrl::parse("ws://relay2.io").unwrap(), 806 RelayUrl::parse("ws://relay2.io").unwrap(),
731 ], 807 ],
732 blossoms: vec![], 808 blossoms: vec![],
809 hashtags: vec![],
733 trusted_maintainer: TEST_KEY_1_KEYS.public_key(), 810 trusted_maintainer: TEST_KEY_1_KEYS.public_key(),
734 maintainers_without_annoucnement: None, 811 maintainers_without_annoucnement: None,
735 maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], 812 maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()],