tangle_indexer


git clone https://radroots.dev/git/tangle_indexer.git
Log | Files | Refs | Submodules | LICENSE

profile.rs (4459B)


      1 use crate::domain::events::ToRadrootsProfileEventIndex;
      2 use crate::relay::event::RelayIndexerEvent;
      3 use crate::utils::nostr::normalize_nip05;
      4 use std::collections::BTreeMap;
      5 
      6 #[derive(Clone)]
      7 struct Nip05Info {
      8     full: String,
      9     local: String,
     10     index_key: String,
     11 }
     12 
     13 #[derive(Default, Clone)]
     14 pub struct ProfileResolver {
     15     author_to_nip05: BTreeMap<String, Nip05Info>,
     16 }
     17 
     18 impl ProfileResolver {
     19     pub fn from_metadata(raw_metadata: &[RelayIndexerEvent]) -> Self {
     20         let mut latest: BTreeMap<String, (u32, String, Nip05Info)> = BTreeMap::new();
     21 
     22         for raw in raw_metadata {
     23             if let Ok(evt) = raw.to_radroots_profile_event() {
     24                 if let Some(n) = &evt.metadata.profile.nip05 {
     25                     let (full, local, index_key) = normalize_nip05(n);
     26                     if index_key.is_empty() {
     27                         continue;
     28                     }
     29 
     30                     let author = evt.event.author.to_lowercase();
     31                     let ts: u32 = evt.metadata.published_at;
     32                     let event_id = evt.event.id.clone();
     33                     let should_replace = match latest.get(&author) {
     34                         None => true,
     35                         Some((old_ts, old_id, _)) => {
     36                             ts > *old_ts || (ts == *old_ts && event_id < *old_id)
     37                         }
     38                     };
     39                     if should_replace {
     40                         latest.insert(
     41                             author,
     42                             (
     43                                 ts,
     44                                 event_id,
     45                                 Nip05Info {
     46                                     full,
     47                                     local,
     48                                     index_key,
     49                                 },
     50                             ),
     51                         );
     52                     }
     53                 }
     54             }
     55         }
     56 
     57         let author_to_nip05 = latest
     58             .into_iter()
     59             .map(|(a, (_ts, _id, n))| (a, n))
     60             .collect();
     61 
     62         Self { author_to_nip05 }
     63     }
     64 
     65     #[inline]
     66     pub fn nip05_for_author(&self, author_hex: &str) -> Option<&str> {
     67         self.author_to_nip05
     68             .get(author_hex)
     69             .map(|info| info.index_key.as_str())
     70     }
     71 
     72     #[inline]
     73     pub fn nip05_full_for_author(&self, author_hex: &str) -> Option<&str> {
     74         self.author_to_nip05
     75             .get(author_hex)
     76             .map(|info| info.full.as_str())
     77     }
     78 
     79     #[inline]
     80     pub fn nip05_local_for_author(&self, author_hex: &str) -> Option<&str> {
     81         self.author_to_nip05
     82             .get(author_hex)
     83             .map(|info| info.local.as_str())
     84     }
     85 }
     86 
     87 #[cfg(test)]
     88 mod tests {
     89     use super::ProfileResolver;
     90     use crate::domain::indexer::kind::IndexerEventKind;
     91     use crate::relay::event::RelayIndexerEvent;
     92 
     93     fn make_profile_event(id: &str, author: &str, created_at: u32, nip05: &str) -> RelayIndexerEvent {
     94         let content = format!(r#"{{"name":"user","nip05":"{}"}}"#, nip05);
     95         RelayIndexerEvent {
     96             id: id.to_string(),
     97             author: author.to_string(),
     98             created_at,
     99             pubkey: author.to_string(),
    100             kind: IndexerEventKind::Profile,
    101             tags: Vec::new(),
    102             content,
    103             hash: id.to_string(),
    104             sig: "sig".to_string(),
    105         }
    106     }
    107 
    108     #[test]
    109     fn resolver_tiebreaks_by_event_id() {
    110         let author = "a".repeat(64);
    111         let high = make_profile_event("f".repeat(64).as_str(), &author, 10, "high@radroots.market");
    112         let low = make_profile_event("0".repeat(64).as_str(), &author, 10, "low@radroots.market");
    113 
    114         let resolver = ProfileResolver::from_metadata(&[high, low]);
    115         let full = resolver
    116             .nip05_full_for_author(&author)
    117             .expect("full nip05");
    118         assert_eq!(full, "low@radroots.market");
    119     }
    120 
    121     #[test]
    122     fn resolver_returns_local_and_index_key() {
    123         let author = "b".repeat(64);
    124         let event = make_profile_event("1".repeat(64).as_str(), &author, 10, "user@radroots.market");
    125         let resolver = ProfileResolver::from_metadata(&[event]);
    126         assert_eq!(
    127             resolver.nip05_full_for_author(&author),
    128             Some("user@radroots.market")
    129         );
    130         assert_eq!(resolver.nip05_local_for_author(&author), Some("user"));
    131         assert_eq!(resolver.nip05_for_author(&author), Some("user"));
    132     }
    133 }