nostr_database/events/
mod.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Copyright (c) 2023-2025 Rust Nostr Developers
3// Distributed under the MIT software license
4
5use std::collections::{BTreeSet, HashMap, HashSet};
6use std::fmt;
7use std::sync::Arc;
8
9use nostr::prelude::*;
10
11pub mod helper;
12
13use crate::{DatabaseError, Events, Profile};
14
15/// NIP65 relays map
16pub type RelaysMap = HashMap<RelayUrl, Option<RelayMetadata>>;
17
18/// Database event status
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum DatabaseEventStatus {
21    /// The event is saved into the database
22    Saved,
23    /// The event is marked as deleted
24    Deleted,
25    /// The event doesn't exist
26    NotExistent,
27}
28
29/// Reason why event wasn't stored into the database
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum RejectedReason {
32    /// Ephemeral events aren't expected to be stored
33    Ephemeral,
34    /// The event already exists
35    Duplicate,
36    /// The event was deleted
37    Deleted,
38    /// The event is expired
39    Expired,
40    /// The event was replaced
41    Replaced,
42    /// Attempt to delete a non-owned event
43    InvalidDelete,
44    /// Other reason
45    Other,
46}
47
48/// Save event status
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub enum SaveEventStatus {
51    /// The event has been successfully saved
52    Success,
53    /// The event has been rejected
54    Rejected(RejectedReason),
55}
56
57impl SaveEventStatus {
58    /// Check if event is successfully saved
59    #[inline]
60    pub fn is_success(&self) -> bool {
61        matches!(self, Self::Success)
62    }
63}
64
65#[doc(hidden)]
66pub trait IntoNostrEventsDatabase {
67    fn into_database(self) -> Arc<dyn NostrEventsDatabase>;
68}
69
70impl IntoNostrEventsDatabase for Arc<dyn NostrEventsDatabase> {
71    fn into_database(self) -> Arc<dyn NostrEventsDatabase> {
72        self
73    }
74}
75
76impl<T> IntoNostrEventsDatabase for T
77where
78    T: NostrEventsDatabase + Sized + 'static,
79{
80    fn into_database(self) -> Arc<dyn NostrEventsDatabase> {
81        Arc::new(self)
82    }
83}
84
85impl<T> IntoNostrEventsDatabase for Arc<T>
86where
87    T: NostrEventsDatabase + 'static,
88{
89    fn into_database(self) -> Arc<dyn NostrEventsDatabase> {
90        self
91    }
92}
93
94/// Nostr Events Database
95///
96/// Store for the nostr events.
97pub trait NostrEventsDatabase: fmt::Debug + Send + Sync {
98    /// Save [`Event`] into store
99    ///
100    /// **This method assumes that [`Event`] was already verified**
101    fn save_event<'a>(
102        &'a self,
103        event: &'a Event,
104    ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>>;
105
106    /// Check event status by ID
107    ///
108    /// Check if the event is saved, deleted or not existent.
109    fn check_id<'a>(
110        &'a self,
111        event_id: &'a EventId,
112    ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>>;
113
114    // TODO: rename to `check_coordinate`?
115    /// Check if [`Coordinate`] has been deleted before a certain [`Timestamp`]
116    fn has_coordinate_been_deleted<'a>(
117        &'a self,
118        coordinate: &'a CoordinateBorrow<'a>,
119        timestamp: &'a Timestamp,
120    ) -> BoxedFuture<'a, Result<bool, DatabaseError>>;
121
122    /// Get [`Event`] by [`EventId`]
123    fn event_by_id<'a>(
124        &'a self,
125        event_id: &'a EventId,
126    ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>>;
127
128    /// Count the number of events found with [`Filter`].
129    ///
130    /// Use `Filter::new()` or `Filter::default()` to count all events.
131    fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>>;
132
133    /// Query stored events.
134    fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>>;
135
136    /// Get `negentropy` items
137    fn negentropy_items(
138        &self,
139        filter: Filter,
140    ) -> BoxedFuture<Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
141        Box::pin(async move {
142            let events: Events = self.query(filter).await?;
143            Ok(events.into_iter().map(|e| (e.id, e.created_at)).collect())
144        })
145    }
146
147    /// Delete all events that match the [Filter]
148    fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>>;
149}
150
151/// Nostr Event Store Extension
152pub trait NostrEventsDatabaseExt: NostrEventsDatabase {
153    /// Get public key metadata
154    fn metadata(
155        &self,
156        public_key: PublicKey,
157    ) -> BoxedFuture<Result<Option<Metadata>, DatabaseError>> {
158        Box::pin(async move {
159            let filter = Filter::new()
160                .author(public_key)
161                .kind(Kind::Metadata)
162                .limit(1);
163            let events: Events = self.query(filter).await?;
164            match events.first_owned() {
165                Some(event) => Ok(Some(
166                    Metadata::from_json(event.content).map_err(DatabaseError::backend)?,
167                )),
168                None => Ok(None),
169            }
170        })
171    }
172
173    /// Get contact list public keys
174    fn contacts_public_keys(
175        &self,
176        public_key: PublicKey,
177    ) -> BoxedFuture<Result<HashSet<PublicKey>, DatabaseError>> {
178        Box::pin(async move {
179            let filter = Filter::new()
180                .author(public_key)
181                .kind(Kind::ContactList)
182                .limit(1);
183            let events: Events = self.query(filter).await?;
184            match events.first_owned() {
185                Some(event) => Ok(event.tags.public_keys().copied().collect()),
186                None => Ok(HashSet::new()),
187            }
188        })
189    }
190
191    /// Get contact list with metadata of [`PublicKey`]
192    fn contacts(
193        &self,
194        public_key: PublicKey,
195    ) -> BoxedFuture<Result<BTreeSet<Profile>, DatabaseError>> {
196        Box::pin(async move {
197            let filter = Filter::new()
198                .author(public_key)
199                .kind(Kind::ContactList)
200                .limit(1);
201            let events: Events = self.query(filter).await?;
202            match events.first_owned() {
203                Some(event) => {
204                    // Get contacts metadata
205                    let filter = Filter::new()
206                        .authors(event.tags.public_keys().copied())
207                        .kind(Kind::Metadata);
208                    let mut contacts: HashSet<Profile> = self
209                        .query(filter)
210                        .await?
211                        .into_iter()
212                        .map(|e| {
213                            let metadata: Metadata =
214                                Metadata::from_json(&e.content).unwrap_or_default();
215                            Profile::new(e.pubkey, metadata)
216                        })
217                        .collect();
218
219                    // Extend with missing public keys
220                    contacts.extend(event.tags.public_keys().copied().map(Profile::from));
221
222                    Ok(contacts.into_iter().collect())
223                }
224                None => Ok(BTreeSet::new()),
225            }
226        })
227    }
228
229    /// Get relays list for [PublicKey]
230    ///
231    /// <https://github.com/nostr-protocol/nips/blob/master/65.md>
232    fn relay_list(&self, public_key: PublicKey) -> BoxedFuture<Result<RelaysMap, DatabaseError>> {
233        Box::pin(async move {
234            // Query
235            let filter: Filter = Filter::default()
236                .author(public_key)
237                .kind(Kind::RelayList)
238                .limit(1);
239            let events: Events = self.query(filter).await?;
240
241            // Extract relay list (NIP65)
242            match events.first_owned() {
243                Some(event) => Ok(nip65::extract_owned_relay_list(event).collect()),
244                None => Ok(HashMap::new()),
245            }
246        })
247    }
248
249    /// Get relays list for public keys
250    ///
251    /// <https://github.com/nostr-protocol/nips/blob/master/65.md>
252    fn relay_lists<'a, I>(
253        &'a self,
254        public_keys: I,
255    ) -> BoxedFuture<'a, Result<HashMap<PublicKey, RelaysMap>, DatabaseError>>
256    where
257        I: IntoIterator<Item = PublicKey> + Send + 'a,
258    {
259        Box::pin(async move {
260            // Query
261            let filter: Filter = Filter::default().authors(public_keys).kind(Kind::RelayList);
262            let events: Events = self.query(filter).await?;
263
264            let mut map = HashMap::with_capacity(events.len());
265
266            for event in events.into_iter() {
267                map.insert(
268                    event.pubkey,
269                    nip65::extract_owned_relay_list(event).collect(),
270                );
271            }
272
273            Ok(map)
274        })
275    }
276}
277
278impl<T: NostrEventsDatabase + ?Sized> NostrEventsDatabaseExt for T {}