upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/client.rs')
-rw-r--r--src/lib/client.rs164
1 files changed, 134 insertions, 30 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs
index e808bea..6f28cff 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -31,6 +31,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, P
31use mockall::*; 31use mockall::*;
32use nostr::{ 32use nostr::{
33 Event, 33 Event,
34 event::{TagKind, TagStandard, UnsignedEvent},
34 filter::Alphabet, 35 filter::Alphabet,
35 nips::{nip01::Coordinate, nip19::Nip19Coordinate}, 36 nips::{nip01::Coordinate, nip19::Nip19Coordinate},
36 signer::SignerBackend, 37 signer::SignerBackend,
@@ -47,20 +48,23 @@ use crate::{
47 get_dirs, 48 get_dirs,
48 git::{Repo, RepoActions, get_git_config_item}, 49 git::{Repo, RepoActions, get_git_config_item},
49 git_events::{ 50 git_events::{
50 event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds, 51 KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter,
52 event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update,
53 status_kinds,
51 }, 54 },
52 login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, 55 login::{get_likely_logged_in_user, user::get_user_ref_from_cache},
53 repo_ref::RepoRef, 56 repo_ref::{RepoRef, normalize_grasp_server_url},
54 repo_state::RepoState, 57 repo_state::RepoState,
55}; 58};
56 59
57#[allow(clippy::struct_field_names)] 60#[allow(clippy::struct_field_names)]
58pub struct Client { 61pub struct Client {
59 client: nostr_sdk::Client, 62 client: nostr_sdk::Client,
60 fallback_relays: Vec<String>, 63 relay_default_set: Vec<String>,
61 more_fallback_relays: Vec<String>, 64 more_fallback_relays: Vec<String>,
62 blaster_relays: Vec<String>, 65 blaster_relays: Vec<String>,
63 fallback_signer_relays: Vec<String>, 66 fallback_signer_relays: Vec<String>,
67 grasp_default_set: Vec<String>,
64 relays_not_to_retry: Arc<RwLock<HashMap<RelayUrl, String>>>, 68 relays_not_to_retry: Arc<RwLock<HashMap<RelayUrl, String>>>,
65} 69}
66 70
@@ -94,10 +98,11 @@ pub trait Connect {
94 async fn set_signer(&mut self, signer: Arc<dyn NostrSigner>); 98 async fn set_signer(&mut self, signer: Arc<dyn NostrSigner>);
95 async fn connect(&self, relay_url: &RelayUrl) -> Result<()>; 99 async fn connect(&self, relay_url: &RelayUrl) -> Result<()>;
96 async fn disconnect(&self) -> Result<()>; 100 async fn disconnect(&self) -> Result<()>;
97 fn get_fallback_relays(&self) -> &Vec<String>; 101 fn get_relay_default_set(&self) -> &Vec<String>;
98 fn get_more_fallback_relays(&self) -> &Vec<String>; 102 fn get_more_fallback_relays(&self) -> &Vec<String>;
99 fn get_blaster_relays(&self) -> &Vec<String>; 103 fn get_blaster_relays(&self) -> &Vec<String>;
100 fn get_fallback_signer_relays(&self) -> &Vec<String>; 104 fn get_fallback_signer_relays(&self) -> &Vec<String>;
105 fn get_grasp_default_set(&self) -> &Vec<String>;
101 async fn send_event_to<'a>( 106 async fn send_event_to<'a>(
102 &self, 107 &self,
103 git_repo_path: Option<&'a Path>, 108 git_repo_path: Option<&'a Path>,
@@ -147,10 +152,11 @@ impl Connect for Client {
147 .opts(Options::new().relay_limits(RelayLimits::disable())) 152 .opts(Options::new().relay_limits(RelayLimits::disable()))
148 .build() 153 .build()
149 }, 154 },
150 fallback_relays: opts.fallback_relays, 155 relay_default_set: opts.relay_default_set,
151 more_fallback_relays: opts.more_fallback_relays, 156 more_fallback_relays: opts.more_fallback_relays,
152 blaster_relays: opts.blaster_relays, 157 blaster_relays: opts.blaster_relays,
153 fallback_signer_relays: opts.fallback_signer_relays, 158 fallback_signer_relays: opts.fallback_signer_relays,
159 grasp_default_set: opts.grasp_default_set,
154 relays_not_to_retry: Arc::new(RwLock::new(HashMap::new())), 160 relays_not_to_retry: Arc::new(RwLock::new(HashMap::new())),
155 } 161 }
156 } 162 }
@@ -189,8 +195,8 @@ impl Connect for Client {
189 Ok(()) 195 Ok(())
190 } 196 }
191 197
192 fn get_fallback_relays(&self) -> &Vec<String> { 198 fn get_relay_default_set(&self) -> &Vec<String> {
193 &self.fallback_relays 199 &self.relay_default_set
194 } 200 }
195 201
196 fn get_more_fallback_relays(&self) -> &Vec<String> { 202 fn get_more_fallback_relays(&self) -> &Vec<String> {
@@ -205,6 +211,10 @@ impl Connect for Client {
205 &self.fallback_signer_relays 211 &self.fallback_signer_relays
206 } 212 }
207 213
214 fn get_grasp_default_set(&self) -> &Vec<String> {
215 &self.grasp_default_set
216 }
217
208 async fn send_event_to<'a>( 218 async fn send_event_to<'a>(
209 &self, 219 &self,
210 git_repo_path: Option<&'a Path>, 220 git_repo_path: Option<&'a Path>,
@@ -335,8 +345,8 @@ impl Connect for Client {
335 trusted_maintainer_coordinate: Option<&'a Nip19Coordinate>, 345 trusted_maintainer_coordinate: Option<&'a Nip19Coordinate>,
336 user_profiles: &HashSet<PublicKey>, 346 user_profiles: &HashSet<PublicKey>,
337 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> { 347 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> {
338 let fallback_relays = &self 348 let relay_default_set = &self
339 .fallback_relays 349 .relay_default_set
340 .iter() 350 .iter()
341 .filter_map(|r| RelayUrl::parse(r).ok()) 351 .filter_map(|r| RelayUrl::parse(r).ok())
342 .collect::<HashSet<RelayUrl>>(); 352 .collect::<HashSet<RelayUrl>>();
@@ -345,7 +355,7 @@ impl Connect for Client {
345 git_repo_path, 355 git_repo_path,
346 trusted_maintainer_coordinate, 356 trusted_maintainer_coordinate,
347 user_profiles, 357 user_profiles,
348 fallback_relays.clone(), 358 relay_default_set.clone(),
349 ) 359 )
350 .await?; 360 .await?;
351 361
@@ -685,17 +695,18 @@ async fn get_events_of(
685 695
686pub struct Params { 696pub struct Params {
687 pub keys: Option<nostr::Keys>, 697 pub keys: Option<nostr::Keys>,
688 pub fallback_relays: Vec<String>, 698 pub relay_default_set: Vec<String>,
689 pub more_fallback_relays: Vec<String>, 699 pub more_fallback_relays: Vec<String>,
690 pub blaster_relays: Vec<String>, 700 pub blaster_relays: Vec<String>,
691 pub fallback_signer_relays: Vec<String>, 701 pub fallback_signer_relays: Vec<String>,
702 pub grasp_default_set: Vec<String>,
692} 703}
693 704
694impl Default for Params { 705impl Default for Params {
695 fn default() -> Self { 706 fn default() -> Self {
696 Params { 707 Params {
697 keys: None, 708 keys: None,
698 fallback_relays: if std::env::var("NGITTEST").is_ok() { 709 relay_default_set: if std::env::var("NGITTEST").is_ok() {
699 vec![ 710 vec![
700 "ws://localhost:8051".to_string(), 711 "ws://localhost:8051".to_string(),
701 "ws://localhost:8052".to_string(), 712 "ws://localhost:8052".to_string(),
@@ -731,6 +742,11 @@ impl Default for Params {
731 } else { 742 } else {
732 vec!["wss://relay.nsec.app".to_string()] 743 vec!["wss://relay.nsec.app".to_string()]
733 }, 744 },
745 grasp_default_set: if std::env::var("NGITTEST").is_ok() {
746 vec![]
747 } else {
748 vec!["relay.ngit.dev".to_string(), "gitnostr.com".to_string()]
749 },
734 } 750 }
735 } 751 }
736} 752}
@@ -749,7 +765,7 @@ impl Params {
749 .collect(); 765 .collect();
750 // elsewhere it is assumed this isn't empty 766 // elsewhere it is assumed this isn't empty
751 if !new_default_relays.is_empty() { 767 if !new_default_relays.is_empty() {
752 params.fallback_relays = new_default_relays; 768 params.relay_default_set = new_default_relays;
753 } 769 }
754 } 770 }
755 if let Ok(Some(relay_blasters)) = 771 if let Ok(Some(relay_blasters)) =
@@ -770,6 +786,17 @@ impl Params {
770 .map(|relay_url| relay_url.to_string()) // Convert RelayUrl back to String 786 .map(|relay_url| relay_url.to_string()) // Convert RelayUrl back to String
771 .collect(); 787 .collect();
772 } 788 }
789 if let Ok(Some(grasp_default_servers)) =
790 get_git_config_item(git_repo, "nostr.grasp-default-set")
791 {
792 let new_default_grasp_servers: Vec<String> = grasp_default_servers
793 .split(';')
794 .filter_map(|url| normalize_grasp_server_url(url).ok()) // Attempt to parse and filter out errors
795 .collect();
796 if !new_default_grasp_servers.is_empty() {
797 params.grasp_default_set = new_default_grasp_servers;
798 }
799 }
773 } 800 }
774 params 801 params
775 } 802 }
@@ -811,6 +838,30 @@ pub async fn sign_event(
811 } 838 }
812} 839}
813 840
841pub async fn sign_draft_event(
842 draft_event: UnsignedEvent,
843 signer: &Arc<dyn NostrSigner>,
844 description: String,
845) -> Result<nostr::Event> {
846 if signer.backend() == SignerBackend::NostrConnect {
847 let term = console::Term::stderr();
848 term.write_line(&format!(
849 "signing event ({description}) with remote signer..."
850 ))?;
851 let event = signer
852 .sign_event(draft_event)
853 .await
854 .context("failed to sign event")?;
855 term.clear_last_lines(1)?;
856 Ok(event)
857 } else {
858 signer
859 .sign_event(draft_event)
860 .await
861 .context("failed to sign event")
862 }
863}
864
814pub async fn fetch_public_key(signer: &Arc<dyn NostrSigner>) -> Result<nostr::PublicKey> { 865pub async fn fetch_public_key(signer: &Arc<dyn NostrSigner>) -> Result<nostr::PublicKey> {
815 if signer.backend() == SignerBackend::NostrConnect { 866 if signer.backend() == SignerBackend::NostrConnect {
816 let term = console::Term::stderr(); 867 let term = console::Term::stderr();
@@ -1459,7 +1510,7 @@ async fn process_fetched_events(
1459 report.updated_state = Some((event.created_at, event.id)); 1510 report.updated_state = Some((event.created_at, event.id));
1460 } 1511 }
1461 } 1512 }
1462 } else if event_is_patch_set_root(event) { 1513 } else if event_is_patch_set_root(event) || event.kind.eq(&KIND_PULL_REQUEST) {
1463 fresh_proposal_roots.insert(event.id); 1514 fresh_proposal_roots.insert(event.id);
1464 report.proposals.insert(event.id); 1515 report.proposals.insert(event.id);
1465 if !request.contributors.contains(&event.pubkey) 1516 if !request.contributors.contains(&event.pubkey)
@@ -1487,12 +1538,23 @@ async fn process_fetched_events(
1487 } 1538 }
1488 for event in &events { 1539 for event in &events {
1489 if !request.existing_events.contains(&event.id) 1540 if !request.existing_events.contains(&event.id)
1490 && !event 1541 && (!event
1491 .tags 1542 .tags
1492 .event_ids() 1543 .event_ids()
1493 .any(|id| report.proposals.contains(id)) 1544 .any(|id| report.proposals.contains(id))
1545 || event
1546 .tags
1547 .filter_standardized(TagKind::Custom(std::borrow::Cow::Borrowed("E")))
1548 .filter_map(|t| match t {
1549 TagStandard::Event { event_id, .. } => Some(event_id),
1550 TagStandard::EventReport(event_id, ..) => Some(event_id),
1551 _ => None,
1552 })
1553 .any(|id| report.proposals.contains(id)))
1494 { 1554 {
1495 if event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event) { 1555 if (event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event))
1556 || event.kind.eq(&KIND_PULL_REQUEST_UPDATE)
1557 {
1496 report.commits.insert(event.id); 1558 report.commits.insert(event.id);
1497 } else if status_kinds().contains(&event.kind) { 1559 } else if status_kinds().contains(&event.kind) {
1498 report.statuses.insert(event.id); 1560 report.statuses.insert(event.id);
@@ -1570,7 +1632,7 @@ pub fn get_fetch_filters(
1570 get_filter_state_events(repo_coordinates), 1632 get_filter_state_events(repo_coordinates),
1571 get_filter_repo_events(repo_coordinates), 1633 get_filter_repo_events(repo_coordinates),
1572 nostr::Filter::default() 1634 nostr::Filter::default()
1573 .kinds(vec![Kind::GitPatch, Kind::EventDeletion]) 1635 .kinds(vec![Kind::GitPatch, Kind::EventDeletion, KIND_PULL_REQUEST])
1574 .custom_tags( 1636 .custom_tags(
1575 SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), 1637 SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
1576 repo_coordinates 1638 repo_coordinates
@@ -1584,15 +1646,29 @@ pub fn get_fetch_filters(
1584 vec![] 1646 vec![]
1585 } else { 1647 } else {
1586 vec![ 1648 vec![
1587 nostr::Filter::default() 1649 nostr::Filter::default().events(proposal_ids.clone()).kinds(
1588 .events(proposal_ids.clone()) 1650 [
1589 .kinds([vec![Kind::GitPatch, Kind::EventDeletion], status_kinds()].concat()), 1651 vec![
1652 Kind::GitPatch,
1653 Kind::EventDeletion,
1654 KIND_PULL_REQUEST_UPDATE,
1655 ],
1656 status_kinds(),
1657 ]
1658 .concat(),
1659 ),
1590 nostr::Filter::default() 1660 nostr::Filter::default()
1591 .custom_tags( 1661 .custom_tags(
1592 SingleLetterTag::uppercase(Alphabet::E), 1662 SingleLetterTag::uppercase(Alphabet::E),
1593 proposal_ids.clone(), 1663 proposal_ids.clone(),
1594 ) 1664 )
1595 .kinds([vec![Kind::GitPatch, Kind::EventDeletion], status_kinds()].concat()), 1665 .kinds(
1666 [
1667 vec![Kind::EventDeletion, KIND_PULL_REQUEST_UPDATE],
1668 status_kinds(),
1669 ]
1670 .concat(),
1671 ),
1596 ] 1672 ]
1597 }, 1673 },
1598 if required_profiles.is_empty() { 1674 if required_profiles.is_empty() {
@@ -1784,7 +1860,7 @@ pub async fn get_proposals_and_revisions_from_cache(
1784 git_repo_path, 1860 git_repo_path,
1785 vec![ 1861 vec![
1786 nostr::Filter::default() 1862 nostr::Filter::default()
1787 .kind(nostr::Kind::GitPatch) 1863 .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST])
1788 .custom_tags( 1864 .custom_tags(
1789 nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), 1865 nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
1790 repo_coordinates 1866 repo_coordinates
@@ -1796,7 +1872,8 @@ pub async fn get_proposals_and_revisions_from_cache(
1796 ) 1872 )
1797 .await? 1873 .await?
1798 .iter() 1874 .iter()
1799 .filter(|e| event_is_patch_set_root(e)) 1875 .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST))
1876 .filter(|e| e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e))
1800 .cloned() 1877 .cloned()
1801 .collect::<Vec<nostr::Event>>(); 1878 .collect::<Vec<nostr::Event>>();
1802 proposals.sort_by_key(|e| e.created_at); 1879 proposals.sort_by_key(|e| e.created_at);
@@ -1804,7 +1881,7 @@ pub async fn get_proposals_and_revisions_from_cache(
1804 Ok(proposals) 1881 Ok(proposals)
1805} 1882}
1806 1883
1807pub async fn get_all_proposal_patch_events_from_cache( 1884pub async fn get_all_proposal_patch_pr_pr_update_events_from_cache(
1808 git_repo_path: &Path, 1885 git_repo_path: &Path,
1809 repo_ref: &RepoRef, 1886 repo_ref: &RepoRef,
1810 proposal_id: &nostr::EventId, 1887 proposal_id: &nostr::EventId,
@@ -1813,10 +1890,21 @@ pub async fn get_all_proposal_patch_events_from_cache(
1813 git_repo_path, 1890 git_repo_path,
1814 vec![ 1891 vec![
1815 nostr::Filter::default() 1892 nostr::Filter::default()
1816 .kind(nostr::Kind::GitPatch) 1893 .kinds([
1894 nostr::Kind::GitPatch,
1895 KIND_PULL_REQUEST,
1896 KIND_PULL_REQUEST_UPDATE,
1897 ])
1817 .event(*proposal_id), 1898 .event(*proposal_id),
1818 nostr::Filter::default() 1899 nostr::Filter::default()
1819 .kind(nostr::Kind::GitPatch) 1900 .kinds([
1901 nostr::Kind::GitPatch,
1902 KIND_PULL_REQUEST,
1903 KIND_PULL_REQUEST_UPDATE,
1904 ])
1905 .custom_tag(SingleLetterTag::uppercase(Alphabet::E), *proposal_id),
1906 nostr::Filter::default()
1907 .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST])
1820 .id(*proposal_id), 1908 .id(*proposal_id),
1821 ], 1909 ],
1822 ) 1910 )
@@ -1836,7 +1924,11 @@ pub async fn get_all_proposal_patch_events_from_cache(
1836 .iter() 1924 .iter()
1837 .copied() 1925 .copied()
1838 .collect(); 1926 .collect();
1839 commit_events.retain(|e| permissioned_users.contains(&e.pubkey)); 1927
1928 commit_events.retain(|e| {
1929 permissioned_users.contains(&e.pubkey)
1930 && (e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e))
1931 });
1840 1932
1841 let revision_roots: HashSet<nostr::EventId> = commit_events 1933 let revision_roots: HashSet<nostr::EventId> = commit_events
1842 .iter() 1934 .iter()
@@ -1849,8 +1941,20 @@ pub async fn get_all_proposal_patch_events_from_cache(
1849 git_repo_path, 1941 git_repo_path,
1850 vec![ 1942 vec![
1851 nostr::Filter::default() 1943 nostr::Filter::default()
1852 .kind(nostr::Kind::GitPatch) 1944 .kinds([
1853 .events(revision_roots) 1945 nostr::Kind::GitPatch,
1946 KIND_PULL_REQUEST,
1947 KIND_PULL_REQUEST_UPDATE,
1948 ])
1949 .events(revision_roots.clone())
1950 .authors(permissioned_users.clone()),
1951 nostr::Filter::default()
1952 .kinds([
1953 nostr::Kind::GitPatch,
1954 KIND_PULL_REQUEST,
1955 KIND_PULL_REQUEST_UPDATE,
1956 ])
1957 .custom_tags(SingleLetterTag::uppercase(Alphabet::E), revision_roots)
1854 .authors(permissioned_users.clone()), 1958 .authors(permissioned_users.clone()),
1855 ], 1959 ],
1856 ) 1960 )
@@ -1891,7 +1995,7 @@ pub async fn send_events(
1891 silent: bool, 1995 silent: bool,
1892) -> Result<()> { 1996) -> Result<()> {
1893 let fallback = [ 1997 let fallback = [
1894 client.get_fallback_relays().clone(), 1998 client.get_relay_default_set().clone(),
1895 if events.iter().any(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) { 1999 if events.iter().any(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) {
1896 client.get_blaster_relays().clone() 2000 client.get_blaster_relays().clone()
1897 } else { 2001 } else {