diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-08-06 12:52:59 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-08-07 17:25:50 +0100 |
| commit | a9b2ebf8216be34950e54dd9a446dbdc0c9c744a (patch) | |
| tree | 5a103933852fbcfcd42b13716cb92eeca5325d6d /src/lib/login | |
| parent | 29f61ffdf155ea88b8d9aec23d28cf70baba577e (diff) | |
feat(send): PR fallback to user / custom grasp
if use is maintainer, push PR to all repo git servers.
if user has a fork, push to all git servers it lists, and repo
grasp servers.
if user hasn't got a fork but has a user grasp list and pushing
push to repo grasp servers fails, create a personal-fork
automatically at each user grasp server and push there.
fallback to prompting user for either grasp servers or git server
with write permission.
if user provides grasp servers, suggesting adding to user preference
list.
Diffstat (limited to 'src/lib/login')
| -rw-r--r-- | src/lib/login/user.rs | 77 |
1 files changed, 74 insertions, 3 deletions
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 @@ | |||
| 1 | use std::{collections::HashSet, path::Path}; | 1 | use std::{collections::HashSet, path::Path, sync::Arc}; |
| 2 | 2 | ||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use nostr::PublicKey; | 4 | use nostr::{PublicKey, Url, event::Tag, signer::NostrSigner}; |
| 5 | use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32}; | 5 | use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32}; |
| 6 | use serde::{self, Deserialize, Serialize}; | 6 | use serde::{self, Deserialize, Serialize}; |
| 7 | 7 | ||
| @@ -9,13 +9,17 @@ use serde::{self, Deserialize, Serialize}; | |||
| 9 | use crate::client::Client; | 9 | use crate::client::Client; |
| 10 | #[cfg(test)] | 10 | #[cfg(test)] |
| 11 | use crate::client::MockConnect; | 11 | use crate::client::MockConnect; |
| 12 | use crate::client::{Connect, get_event_from_global_cache}; | 12 | use 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)] |
| 15 | pub struct UserRef { | 18 | pub 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)] |
| 56 | pub struct UserGraspList { | ||
| 57 | pub urls: Vec<Url>, | ||
| 58 | pub created_at: Timestamp, | ||
| 59 | } | ||
| 60 | |||
| 61 | impl 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)] | ||
| 52 | pub struct UserRelayRef { | 85 | pub 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 | |||
| 257 | pub 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 | } | ||