1use 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
15pub type RelaysMap = HashMap<RelayUrl, Option<RelayMetadata>>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub enum DatabaseEventStatus {
21 Saved,
23 Deleted,
25 NotExistent,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum RejectedReason {
32 Ephemeral,
34 Duplicate,
36 Deleted,
38 Expired,
40 Replaced,
42 InvalidDelete,
44 Other,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub enum SaveEventStatus {
51 Success,
53 Rejected(RejectedReason),
55}
56
57impl SaveEventStatus {
58 #[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
94pub trait NostrEventsDatabase: fmt::Debug + Send + Sync {
98 fn save_event<'a>(
102 &'a self,
103 event: &'a Event,
104 ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>>;
105
106 fn check_id<'a>(
110 &'a self,
111 event_id: &'a EventId,
112 ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>>;
113
114 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 fn event_by_id<'a>(
124 &'a self,
125 event_id: &'a EventId,
126 ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>>;
127
128 fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>>;
132
133 fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>>;
135
136 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 fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>>;
149}
150
151pub trait NostrEventsDatabaseExt: NostrEventsDatabase {
153 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 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 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 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 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 fn relay_list(&self, public_key: PublicKey) -> BoxedFuture<Result<RelaysMap, DatabaseError>> {
233 Box::pin(async move {
234 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 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 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 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 {}