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:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-11-21 16:53:17 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-11-21 16:53:17 +0000
commitf79014235e85554e3661b3f2a02b8fa88bc192ff (patch)
treefceec3ff2df212148a3420af7cef81a3f818463e /src/lib/client.rs
parent91b0eac4daf92b7b740267ef203a1a8ba591974b (diff)
feat(login): overhaul login experience
* simplify login menu, making it more accessable to newcomers and easier to select remote signer options * enable `ngit login` to work from anywhere (not just a git repo) * assume fresh login details saved to global git config but fallback to local repository * maintain local repository login via `ngit login --local` * maintain login via CLI arguments eg `ngit send --nsec nsec123` * nudge users to remember nsec when pasting in ncryptsec for a better UX, whilst maintaining the option to be prompted for password everytime * create placeholder menu items for help menu and create account
Diffstat (limited to 'src/lib/client.rs')
-rw-r--r--src/lib/client.rs174
1 files changed, 104 insertions, 70 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs
index 676fff8..a8b48f4 100644
--- a/src/lib/client.rs
+++ b/src/lib/client.rs
@@ -43,7 +43,7 @@ use crate::{
43 git_events::{ 43 git_events::{
44 event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds, 44 event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds,
45 }, 45 },
46 login::{get_logged_in_user, get_user_ref_from_cache}, 46 login::{get_likely_logged_in_user, user::get_user_ref_from_cache},
47 repo_ref::RepoRef, 47 repo_ref::RepoRef,
48 repo_state::RepoState, 48 repo_state::RepoState,
49}; 49};
@@ -69,9 +69,9 @@ pub trait Connect {
69 fn get_more_fallback_relays(&self) -> &Vec<String>; 69 fn get_more_fallback_relays(&self) -> &Vec<String>;
70 fn get_blaster_relays(&self) -> &Vec<String>; 70 fn get_blaster_relays(&self) -> &Vec<String>;
71 fn get_fallback_signer_relays(&self) -> &Vec<String>; 71 fn get_fallback_signer_relays(&self) -> &Vec<String>;
72 async fn send_event_to( 72 async fn send_event_to<'a>(
73 &self, 73 &self,
74 git_repo_path: &Path, 74 git_repo_path: Option<&'a Path>,
75 url: &str, 75 url: &str,
76 event: nostr::event::Event, 76 event: nostr::event::Event,
77 ) -> Result<nostr::EventId>; 77 ) -> Result<nostr::EventId>;
@@ -86,15 +86,15 @@ pub trait Connect {
86 filters: Vec<nostr::Filter>, 86 filters: Vec<nostr::Filter>,
87 progress_reporter: MultiProgress, 87 progress_reporter: MultiProgress,
88 ) -> Result<(Vec<Result<Vec<nostr::Event>>>, MultiProgress)>; 88 ) -> Result<(Vec<Result<Vec<nostr::Event>>>, MultiProgress)>;
89 async fn fetch_all( 89 async fn fetch_all<'a>(
90 &self, 90 &self,
91 git_repo_path: &Path, 91 git_repo_path: Option<&'a Path>,
92 repo_coordinates: &HashSet<Coordinate>, 92 repo_coordinates: &HashSet<Coordinate>,
93 user_profiles: &HashSet<PublicKey>, 93 user_profiles: &HashSet<PublicKey>,
94 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)>; 94 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)>;
95 async fn fetch_all_from_relay( 95 async fn fetch_all_from_relay<'a>(
96 &self, 96 &self,
97 git_repo_path: &Path, 97 git_repo_path: Option<&'a Path>,
98 request: FetchRequest, 98 request: FetchRequest,
99 pb: &Option<ProgressBar>, 99 pb: &Option<ProgressBar>,
100 ) -> Result<FetchReport>; 100 ) -> Result<FetchReport>;
@@ -214,9 +214,9 @@ impl Connect for Client {
214 &self.fallback_signer_relays 214 &self.fallback_signer_relays
215 } 215 }
216 216
217 async fn send_event_to( 217 async fn send_event_to<'a>(
218 &self, 218 &self,
219 git_repo_path: &Path, 219 git_repo_path: Option<&'a Path>,
220 url: &str, 220 url: &str,
221 event: Event, 221 event: Event,
222 ) -> Result<nostr::EventId> { 222 ) -> Result<nostr::EventId> {
@@ -228,7 +228,9 @@ impl Connect for Client {
228 .await? 228 .await?
229 .send_event(event.clone()) 229 .send_event(event.clone())
230 .await?; 230 .await?;
231 save_event_in_cache(git_repo_path, &event).await?; 231 if let Some(git_repo_path) = git_repo_path {
232 save_event_in_local_cache(git_repo_path, &event).await?;
233 }
232 if event.kind.eq(&Kind::GitRepoAnnouncement) { 234 if event.kind.eq(&Kind::GitRepoAnnouncement) {
233 save_event_in_global_cache(git_repo_path, &event).await?; 235 save_event_in_global_cache(git_repo_path, &event).await?;
234 } 236 }
@@ -324,9 +326,9 @@ impl Connect for Client {
324 } 326 }
325 327
326 #[allow(clippy::too_many_lines)] 328 #[allow(clippy::too_many_lines)]
327 async fn fetch_all( 329 async fn fetch_all<'a>(
328 &self, 330 &self,
329 git_repo_path: &Path, 331 git_repo_path: Option<&'a Path>,
330 repo_coordinates: &HashSet<Coordinate>, 332 repo_coordinates: &HashSet<Coordinate>,
331 user_profiles: &HashSet<PublicKey>, 333 user_profiles: &HashSet<PublicKey>,
332 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> { 334 ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> {
@@ -496,9 +498,9 @@ impl Connect for Client {
496 Ok((relay_reports, progress_reporter)) 498 Ok((relay_reports, progress_reporter))
497 } 499 }
498 500
499 async fn fetch_all_from_relay( 501 async fn fetch_all_from_relay<'a>(
500 &self, 502 &self,
501 git_repo_path: &Path, 503 git_repo_path: Option<&'a Path>,
502 request: FetchRequest, 504 request: FetchRequest,
503 pb: &Option<ProgressBar>, 505 pb: &Option<ProgressBar>,
504 ) -> Result<FetchReport> { 506 ) -> Result<FetchReport> {
@@ -742,20 +744,25 @@ async fn get_local_cache_database(git_repo_path: &Path) -> Result<NostrLMDB> {
742 .context("cannot open or create nostr cache database at .git/nostr-cache.lmdb") 744 .context("cannot open or create nostr cache database at .git/nostr-cache.lmdb")
743} 745}
744 746
745async fn get_global_cache_database(git_repo_path: &Path) -> Result<NostrLMDB> { 747async fn get_global_cache_database(git_repo_path: Option<&Path>) -> Result<NostrLMDB> {
746 NostrLMDB::open(if std::env::var("NGITTEST").is_err() { 748 let path = if std::env::var("NGITTEST").is_ok() {
749 if let Some(git_repo_path) = git_repo_path {
750 git_repo_path.join(".git/test-global-cache.lmdb")
751 } else {
752 bail!("git_repo must be supplied to get_global_cache_database during integration tests")
753 }
754 } else {
747 create_dir_all(get_dirs()?.cache_dir()).context(format!( 755 create_dir_all(get_dirs()?.cache_dir()).context(format!(
748 "cannot create cache directory in: {:?}", 756 "cannot create cache directory in: {:?}",
749 get_dirs()?.cache_dir() 757 get_dirs()?.cache_dir()
750 ))?; 758 ))?;
751 get_dirs()?.cache_dir().join("nostr-cache.lmdb") 759 get_dirs()?.cache_dir().join("nostr-cache.lmdb")
752 } else { 760 };
753 git_repo_path.join(".git/test-global-cache.lmdb") 761
754 }) 762 NostrLMDB::open(path).context("cannot open ngit global nostr cache database")
755 .context("cannot open ngit global nostr cache database")
756} 763}
757 764
758pub async fn get_events_from_cache( 765pub async fn get_events_from_local_cache(
759 git_repo_path: &Path, 766 git_repo_path: &Path,
760 filters: Vec<nostr::Filter>, 767 filters: Vec<nostr::Filter>,
761) -> Result<Vec<nostr::Event>> { 768) -> Result<Vec<nostr::Event>> {
@@ -770,7 +777,7 @@ pub async fn get_events_from_cache(
770} 777}
771 778
772pub async fn get_event_from_global_cache( 779pub async fn get_event_from_global_cache(
773 git_repo_path: &Path, 780 git_repo_path: Option<&Path>,
774 filters: Vec<nostr::Filter>, 781 filters: Vec<nostr::Filter>,
775) -> Result<Vec<nostr::Event>> { 782) -> Result<Vec<nostr::Event>> {
776 Ok(get_global_cache_database(git_repo_path) 783 Ok(get_global_cache_database(git_repo_path)
@@ -781,7 +788,7 @@ pub async fn get_event_from_global_cache(
781 .to_vec()) 788 .to_vec())
782} 789}
783 790
784pub async fn save_event_in_cache(git_repo_path: &Path, event: &nostr::Event) -> Result<bool> { 791pub async fn save_event_in_local_cache(git_repo_path: &Path, event: &nostr::Event) -> Result<bool> {
785 get_local_cache_database(git_repo_path) 792 get_local_cache_database(git_repo_path)
786 .await? 793 .await?
787 .save_event(event) 794 .save_event(event)
@@ -790,7 +797,7 @@ pub async fn save_event_in_cache(git_repo_path: &Path, event: &nostr::Event) ->
790} 797}
791 798
792pub async fn save_event_in_global_cache( 799pub async fn save_event_in_global_cache(
793 git_repo_path: &Path, 800 git_repo_path: Option<&Path>,
794 event: &nostr::Event, 801 event: &nostr::Event,
795) -> Result<bool> { 802) -> Result<bool> {
796 get_global_cache_database(git_repo_path) 803 get_global_cache_database(git_repo_path)
@@ -801,7 +808,7 @@ pub async fn save_event_in_global_cache(
801} 808}
802 809
803pub async fn get_repo_ref_from_cache( 810pub async fn get_repo_ref_from_cache(
804 git_repo_path: &Path, 811 git_repo_path: Option<&Path>,
805 repo_coordinates: &HashSet<Coordinate>, 812 repo_coordinates: &HashSet<Coordinate>,
806) -> Result<RepoRef> { 813) -> Result<RepoRef> {
807 let mut maintainers = HashSet::new(); 814 let mut maintainers = HashSet::new();
@@ -817,7 +824,11 @@ pub async fn get_repo_ref_from_cache(
817 824
818 let events = [ 825 let events = [
819 get_event_from_global_cache(git_repo_path, vec![repo_events_filter.clone()]).await?, 826 get_event_from_global_cache(git_repo_path, vec![repo_events_filter.clone()]).await?,
820 get_events_from_cache(git_repo_path, vec![repo_events_filter]).await?, 827 if let Some(git_repo_path) = git_repo_path {
828 get_events_from_local_cache(git_repo_path, vec![repo_events_filter]).await?
829 } else {
830 vec![]
831 },
821 ] 832 ]
822 .concat(); 833 .concat();
823 for e in events { 834 for e in events {
@@ -866,19 +877,32 @@ pub async fn get_repo_ref_from_cache(
866 }) 877 })
867} 878}
868 879
869pub async fn get_state_from_cache(git_repo_path: &Path, repo_ref: &RepoRef) -> Result<RepoState> { 880pub async fn get_state_from_cache(
870 RepoState::try_from( 881 git_repo_path: Option<&Path>,
871 get_events_from_cache( 882 repo_ref: &RepoRef,
872 git_repo_path, 883) -> Result<RepoState> {
873 vec![get_filter_state_events(&repo_ref.coordinates())], 884 if let Some(git_repo_path) = git_repo_path {
885 RepoState::try_from(
886 get_events_from_local_cache(
887 git_repo_path,
888 vec![get_filter_state_events(&repo_ref.coordinates())],
889 )
890 .await?,
874 ) 891 )
875 .await?, 892 } else {
876 ) 893 RepoState::try_from(
894 get_event_from_global_cache(
895 git_repo_path,
896 vec![get_filter_state_events(&repo_ref.coordinates())],
897 )
898 .await?,
899 )
900 }
877} 901}
878 902
879#[allow(clippy::too_many_lines)] 903#[allow(clippy::too_many_lines)]
880async fn create_relays_request( 904async fn create_relays_request(
881 git_repo_path: &Path, 905 git_repo_path: Option<&Path>,
882 repo_coordinates: &HashSet<Coordinate>, 906 repo_coordinates: &HashSet<Coordinate>,
883 user_profiles: &HashSet<PublicKey>, 907 user_profiles: &HashSet<PublicKey>,
884 fallback_relays: HashSet<Url>, 908 fallback_relays: HashSet<Url>,
@@ -926,25 +950,27 @@ async fn create_relays_request(
926 } 950 }
927 } 951 }
928 952
929 for event in &get_events_from_cache( 953 if let Some(git_repo_path) = git_repo_path {
930 git_repo_path, 954 for event in &get_events_from_local_cache(
931 vec![ 955 git_repo_path,
932 nostr::Filter::default() 956 vec![
933 .kinds(vec![Kind::GitPatch]) 957 nostr::Filter::default()
934 .custom_tag( 958 .kinds(vec![Kind::GitPatch])
935 SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), 959 .custom_tag(
936 repo_coordinates_without_relays 960 SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
937 .iter() 961 repo_coordinates_without_relays
938 .map(std::string::ToString::to_string) 962 .iter()
939 .collect::<Vec<String>>(), 963 .map(std::string::ToString::to_string)
940 ), 964 .collect::<Vec<String>>(),
941 ], 965 ),
942 ) 966 ],
943 .await? 967 )
944 { 968 .await?
945 if event_is_patch_set_root(event) || event_is_revision_root(event) { 969 {
946 proposals.insert(event.id); 970 if event_is_patch_set_root(event) || event_is_revision_root(event) {
947 contributors.insert(event.pubkey); 971 proposals.insert(event.id);
972 contributors.insert(event.pubkey);
973 }
948 } 974 }
949 } 975 }
950 976
@@ -958,7 +984,9 @@ async fn create_relays_request(
958 .iter() 984 .iter()
959 .find(|e| e.kind == Kind::Metadata && e.pubkey.eq(c)) 985 .find(|e| e.kind == Kind::Metadata && e.pubkey.eq(c))
960 { 986 {
961 save_event_in_cache(git_repo_path, event).await?; 987 if let Some(git_repo_path) = git_repo_path {
988 save_event_in_local_cache(git_repo_path, event).await?;
989 }
962 } else { 990 } else {
963 missing_contributor_profiles.insert(c.to_owned()); 991 missing_contributor_profiles.insert(c.to_owned());
964 } 992 }
@@ -967,8 +995,10 @@ async fn create_relays_request(
967 995
968 let profiles_to_fetch_from_user_relays = { 996 let profiles_to_fetch_from_user_relays = {
969 let mut user_profiles = user_profiles.clone(); 997 let mut user_profiles = user_profiles.clone();
970 if let Ok(Some(current_user)) = get_logged_in_user(git_repo_path).await { 998 if let Some(git_repo_path) = git_repo_path {
971 user_profiles.insert(current_user); 999 if let Ok(Some(current_user)) = get_likely_logged_in_user(git_repo_path).await {
1000 user_profiles.insert(current_user);
1001 }
972 } 1002 }
973 let mut map: HashMap<PublicKey, (Timestamp, Timestamp)> = HashMap::new(); 1003 let mut map: HashMap<PublicKey, (Timestamp, Timestamp)> = HashMap::new();
974 for public_key in &user_profiles { 1004 for public_key in &user_profiles {
@@ -1022,12 +1052,14 @@ async fn create_relays_request(
1022 .copied() 1052 .copied()
1023 .collect(), 1053 .collect(),
1024 ) { 1054 ) {
1025 for (id, _) in get_local_cache_database(git_repo_path) 1055 if let Some(git_repo_path) = git_repo_path {
1026 .await? 1056 for (id, _) in get_local_cache_database(git_repo_path)
1027 .negentropy_items(filter) 1057 .await?
1028 .await? 1058 .negentropy_items(filter)
1029 { 1059 .await?
1030 existing_events.insert(id); 1060 {
1061 existing_events.insert(id);
1062 }
1031 } 1063 }
1032 } 1064 }
1033 existing_events 1065 existing_events
@@ -1105,7 +1137,7 @@ async fn create_relays_request(
1105async fn process_fetched_events( 1137async fn process_fetched_events(
1106 events: Vec<nostr::Event>, 1138 events: Vec<nostr::Event>,
1107 request: &FetchRequest, 1139 request: &FetchRequest,
1108 git_repo_path: &Path, 1140 git_repo_path: Option<&Path>,
1109 fresh_coordinates: &mut HashSet<Coordinate>, 1141 fresh_coordinates: &mut HashSet<Coordinate>,
1110 fresh_proposal_roots: &mut HashSet<EventId>, 1142 fresh_proposal_roots: &mut HashSet<EventId>,
1111 fresh_profiles: &mut HashSet<PublicKey>, 1143 fresh_profiles: &mut HashSet<PublicKey>,
@@ -1113,7 +1145,9 @@ async fn process_fetched_events(
1113) -> Result<()> { 1145) -> Result<()> {
1114 for event in &events { 1146 for event in &events {
1115 if !request.existing_events.contains(&event.id) { 1147 if !request.existing_events.contains(&event.id) {
1116 save_event_in_cache(git_repo_path, event).await?; 1148 if let Some(git_repo_path) = git_repo_path {
1149 save_event_in_local_cache(git_repo_path, event).await?;
1150 }
1117 if event.kind.eq(&Kind::GitRepoAnnouncement) { 1151 if event.kind.eq(&Kind::GitRepoAnnouncement) {
1118 save_event_in_global_cache(git_repo_path, event).await?; 1152 save_event_in_global_cache(git_repo_path, event).await?;
1119 let new_coordinate = !request 1153 let new_coordinate = !request
@@ -1488,7 +1522,7 @@ pub async fn fetching_with_report(
1488 let term = console::Term::stderr(); 1522 let term = console::Term::stderr();
1489 term.write_line("fetching updates...")?; 1523 term.write_line("fetching updates...")?;
1490 let (relay_reports, progress_reporter) = client 1524 let (relay_reports, progress_reporter) = client
1491 .fetch_all(git_repo_path, repo_coordinates, &HashSet::new()) 1525 .fetch_all(Some(git_repo_path), repo_coordinates, &HashSet::new())
1492 .await?; 1526 .await?;
1493 if !relay_reports.iter().any(std::result::Result::is_err) { 1527 if !relay_reports.iter().any(std::result::Result::is_err) {
1494 let _ = progress_reporter.clear(); 1528 let _ = progress_reporter.clear();
@@ -1506,7 +1540,7 @@ pub async fn get_proposals_and_revisions_from_cache(
1506 git_repo_path: &Path, 1540 git_repo_path: &Path,
1507 repo_coordinates: HashSet<Coordinate>, 1541 repo_coordinates: HashSet<Coordinate>,
1508) -> Result<Vec<nostr::Event>> { 1542) -> Result<Vec<nostr::Event>> {
1509 let mut proposals = get_events_from_cache( 1543 let mut proposals = get_events_from_local_cache(
1510 git_repo_path, 1544 git_repo_path,
1511 vec![ 1545 vec![
1512 nostr::Filter::default() 1546 nostr::Filter::default()
@@ -1535,7 +1569,7 @@ pub async fn get_all_proposal_patch_events_from_cache(
1535 repo_ref: &RepoRef, 1569 repo_ref: &RepoRef,
1536 proposal_id: &nostr::EventId, 1570 proposal_id: &nostr::EventId,
1537) -> Result<Vec<nostr::Event>> { 1571) -> Result<Vec<nostr::Event>> {
1538 let mut commit_events = get_events_from_cache( 1572 let mut commit_events = get_events_from_local_cache(
1539 git_repo_path, 1573 git_repo_path,
1540 vec![ 1574 vec![
1541 nostr::Filter::default() 1575 nostr::Filter::default()
@@ -1571,7 +1605,7 @@ pub async fn get_all_proposal_patch_events_from_cache(
1571 .collect(); 1605 .collect();
1572 1606
1573 if !revision_roots.is_empty() { 1607 if !revision_roots.is_empty() {
1574 for event in get_events_from_cache( 1608 for event in get_events_from_local_cache(
1575 git_repo_path, 1609 git_repo_path,
1576 vec![ 1610 vec![
1577 nostr::Filter::default() 1611 nostr::Filter::default()
@@ -1594,7 +1628,7 @@ pub async fn get_all_proposal_patch_events_from_cache(
1594} 1628}
1595 1629
1596pub async fn get_event_from_cache_by_id(git_repo: &Repo, event_id: &EventId) -> Result<Event> { 1630pub async fn get_event_from_cache_by_id(git_repo: &Repo, event_id: &EventId) -> Result<Event> {
1597 Ok(get_events_from_cache( 1631 Ok(get_events_from_local_cache(
1598 git_repo.get_path()?, 1632 git_repo.get_path()?,
1599 vec![nostr::Filter::default().id(*event_id)], 1633 vec![nostr::Filter::default().id(*event_id)],
1600 ) 1634 )
@@ -1609,7 +1643,7 @@ pub async fn get_event_from_cache_by_id(git_repo: &Repo, event_id: &EventId) ->
1609pub async fn send_events( 1643pub async fn send_events(
1610 #[cfg(test)] client: &crate::client::MockConnect, 1644 #[cfg(test)] client: &crate::client::MockConnect,
1611 #[cfg(not(test))] client: &Client, 1645 #[cfg(not(test))] client: &Client,
1612 git_repo_path: &Path, 1646 git_repo_path: Option<&Path>,
1613 events: Vec<nostr::Event>, 1647 events: Vec<nostr::Event>,
1614 my_write_relays: Vec<String>, 1648 my_write_relays: Vec<String>,
1615 repo_read_relays: Vec<String>, 1649 repo_read_relays: Vec<String>,