Skip to main content

dialog_lib/
query.rs

1use crate::{Dialog, DialogError, Note, Result};
2use nostr_sdk::prelude::*;
3
4impl Dialog {
5    pub async fn list_notes(&self, limit: usize) -> Result<Vec<Note>> {
6        eprintln!(
7            "[lib] list_notes: limit={} for pubkey={}",
8            limit,
9            self.keys.public_key()
10        );
11        // Query from local database
12        let filter = Filter::new()
13            .author(self.keys.public_key())
14            .kind(Kind::from(1059))
15            .limit(limit);
16
17        let events = self
18            .client
19            .database()
20            .query(vec![filter])
21            .await
22            .map_err(|e| DialogError::Database(e.to_string()))?;
23
24        // Decrypt and convert to Notes
25        let mut notes = Vec::new();
26        for event in events {
27            if let Ok(decrypted) = self.decrypt_event(&event) {
28                let is_read = self.get_read_status(&event.id).await;
29                notes.push(Note {
30                    id: event.id,
31                    text: decrypted,
32                    tags: extract_tags(&event),
33                    created_at: event.created_at,
34                    is_read,
35                    is_synced: true, // If it's in DB, it was synced
36                });
37            }
38        }
39
40        // Sort by created_at descending (newest first)
41        notes.sort_by(|a, b| b.created_at.cmp(&a.created_at));
42
43        eprintln!("[lib] list_notes: returning {} notes", notes.len());
44        Ok(notes)
45    }
46
47    pub async fn list_by_tag(&self, tag: &str, limit: usize) -> Result<Vec<Note>> {
48        let filter = Filter::new()
49            .author(self.keys.public_key())
50            .kind(Kind::from(1059))
51            .hashtag(tag.to_lowercase())
52            .limit(limit);
53
54        let events = self
55            .client
56            .database()
57            .query(vec![filter])
58            .await
59            .map_err(|e| DialogError::Database(e.to_string()))?;
60
61        // Decrypt and convert to Notes
62        let mut notes = Vec::new();
63        for event in events {
64            if let Ok(decrypted) = self.decrypt_event(&event) {
65                let is_read = self.get_read_status(&event.id).await;
66                notes.push(Note {
67                    id: event.id,
68                    text: decrypted,
69                    tags: extract_tags(&event),
70                    created_at: event.created_at,
71                    is_read,
72                    is_synced: true, // If it's in DB, it was synced
73                });
74            }
75        }
76
77        // Sort by created_at descending (newest first)
78        notes.sort_by(|a, b| b.created_at.cmp(&a.created_at));
79
80        Ok(notes)
81    }
82
83    pub async fn sync_notes(&self) -> Result<()> {
84        // Sync with relay using negentropy
85        let filter = Filter::new()
86            .author(self.keys.public_key())
87            .kind(Kind::from(1059));
88
89        self.client.sync(filter, &SyncOptions::default()).await?;
90        Ok(())
91    }
92
93    /// Plain NIP-01 subscribe/fetch fallback for relays without Negentropy
94    pub async fn sync_notes_plain(&self, limit: Option<usize>) -> Result<()> {
95        // Build a standard filter. If a limit is provided, apply it.
96        let mut filter = Filter::new()
97            .author(self.keys.public_key())
98            .kind(Kind::from(1059));
99        if let Some(lim) = limit {
100            filter = filter.limit(lim);
101        }
102
103        // Fetch a snapshot of events and persist to local DB
104        // Try a reasonable timeout; network errors are surfaced as DialogError::Database via save.
105        let events = self
106            .client
107            .fetch_events(vec![filter], Some(std::time::Duration::from_secs(10)))
108            .await
109            .map_err(|e| DialogError::Database(e.to_string()))?;
110
111        for event in events {
112            // Save to local DB; ignore duplicates
113            self.client
114                .database()
115                .save_event(&event)
116                .await
117                .map_err(|e| DialogError::Database(e.to_string()))?;
118        }
119        Ok(())
120    }
121}
122
123fn extract_tags(event: &Event) -> Vec<String> {
124    event
125        .tags
126        .iter()
127        .filter_map(|tag| {
128            if let Some(TagStandard::Hashtag(t)) = tag.as_standardized() {
129                Some(t.to_string())
130            } else {
131                None
132            }
133        })
134        .collect()
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_extract_tags() {
143        // Create a test event with hashtags
144        let test_keys = Keys::generate();
145        let tags = vec![
146            Tag::hashtag("test"),
147            Tag::hashtag("example"),
148            Tag::public_key(test_keys.public_key()),
149        ];
150
151        let sig_bytes = [0u8; 64];
152        let event = Event::new(
153            EventId::all_zeros(),
154            test_keys.public_key(),
155            Timestamp::now(),
156            Kind::from(1059),
157            tags,
158            "encrypted content",
159            Signature::from_slice(&sig_bytes).unwrap(),
160        );
161
162        let extracted = extract_tags(&event);
163        assert_eq!(extracted, vec!["test", "example"]);
164    }
165}