diff --git a/flake.lock b/flake.lock index 66f85e6..6b7a5bd 100644 --- a/flake.lock +++ b/flake.lock @@ -38,10 +38,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1705496572, - "narHash": "sha256-rPIe9G5EBLXdBdn9ilGc0nq082lzQd0xGGe092R/5QE=", - "path": "/nix/store/wcidiyklj0nrljlz5m3qlkvhv8f2ddv8-source", - "rev": "842d9d80cfd4560648c785f8a4e6f3b096790e19", + "lastModified": 1706191920, + "narHash": "sha256-eLihrZAPZX0R6RyM5fYAWeKVNuQPYjAkCUBr+JNvtdE=", + "path": "/nix/store/9s5qs4hni9fj88x79iw6im7amv7ghb76-source", + "rev": "ae5c332cbb5827f6b1f02572496b141021de335f", "type": "path" }, "original": { @@ -51,11 +51,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1705566941, - "narHash": "sha256-CLNtVRDA8eUPk+bxsCCZtRO0Cp+SpHdn1nNOLoFypLs=", + "lastModified": 1706173671, + "narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b06ff4bf8f4ad900fe0c2a61fc2946edc3a84be7", + "rev": "4fddc9be4eaf195d631333908f2a454b03628ee5", "type": "github" }, "original": { diff --git a/src/noose/sqlite.rs b/src/noose/sqlite.rs index c1ae0bd..189dff5 100644 --- a/src/noose/sqlite.rs +++ b/src/noose/sqlite.rs @@ -5,7 +5,9 @@ use crate::{ }; use async_trait::async_trait; use deadpool_sqlite::{Config, Object, Pool, Runtime}; -use nostr::{nips::nip01::Coordinate, Event, EventId, Filter, RelayMessage, Timestamp, Url}; +use nostr::{ + nips::nip01::Coordinate, Event, EventId, Filter, RelayMessage, TagKind, Timestamp, Url, +}; use nostr_database::{Backend, DatabaseOptions, NostrDatabase, Order}; use rusqlite::Row; use sea_query::{extension::sqlite::SqliteExpr, Order as SqOrder, Query, SqliteQueryBuilder}; @@ -544,26 +546,36 @@ impl NostrSqlite { // Insert into Tags table log::debug!("inserting new event into tags"); for tag in event.tags.clone() { - let tag = tag.to_vec(); - if tag.len() >= 2 { - let tag_name = &tag[0]; - let tag_value = &tag[1]; - if tag_name.len() == 1 { - let (sql, values) = Query::insert() - .into_table(TagsTable::Table) - .columns([TagsTable::Tag, TagsTable::Value, TagsTable::EventId]) - .values_panic([ - tag_name.into(), - tag_value.into(), - id.clone().into(), - ]) - .build_rusqlite(SqliteQueryBuilder); + if Self::tag_is_indexable(&tag) { + let tag = tag.to_vec(); + if tag.len() >= 2 { + let tag_name = &tag[0]; + let tag_value = &tag[1]; + if tag_name.len() == 1 { + let (sql, values) = Query::insert() + .into_table(TagsTable::Table) + .columns([ + TagsTable::Tag, + TagsTable::Value, + TagsTable::EventId, + ]) + .values_panic([ + tag_name.into(), + tag_value.into(), + id.clone().into(), + ]) + .build_rusqlite(SqliteQueryBuilder); - if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) { - log::error!("Error inserting event into 'tags' table: {}", err); - tx.rollback().unwrap(); + if let Err(err) = tx.execute(sql.as_str(), &*values.as_params()) + { + log::error!( + "Error inserting event into 'tags' table: {}", + err + ); + tx.rollback().unwrap(); - return Ok(false); + return Ok(false); + } } } } @@ -586,6 +598,22 @@ impl NostrSqlite { event_saved } + fn tag_is_indexable(tag: &nostr::Tag) -> bool { + matches!( + tag.kind(), + TagKind::E + | TagKind::P + | TagKind::UpperP + | TagKind::A + | TagKind::D + | TagKind::G + | TagKind::I + | TagKind::M + | TagKind::R + | TagKind::T + ) + } + async fn has_event_already_been_saved(&self, event_id: &EventId) -> Result { match self.get_event_by_id(*event_id).await { Ok(_) => Ok(true), @@ -1136,7 +1164,55 @@ impl NostrSqlite { coordinate: &Coordinate, timestamp: Timestamp, ) -> Result { - Ok(true) + let Ok(connection) = self.get_connection().await else { + return Err(Error::internal_with_message("Unable to get DB connection")); + }; + + let ident = if coordinate.identifier.is_empty() { + format!("{}:{}", coordinate.kind, coordinate.pubkey) + } else { + format!( + "{}:{}:{}", + coordinate.kind, coordinate.pubkey, coordinate.identifier + ) + }; + + let Ok(query_result) = connection + .interact(move |conn: &mut rusqlite::Connection| -> Result { + let (sql, value) = Query::select() + .from(EventsTable::Table) + .columns([EventsTable::EventId, EventsTable::CreatedAt]) + .left_join( + TagsTable::Table, + sea_query::Expr::col((TagsTable::Table, TagsTable::EventId)) + .equals((EventsTable::Table, EventsTable::EventId)), + ) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("a")) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).eq(ident)) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) + .gte(timestamp.as_i64()), + ) + .limit(1) + .build_rusqlite(SqliteQueryBuilder); + + let mut stmt = conn.prepare(sql.as_str()).unwrap(); + let mut rows = stmt.query(&*value.as_params()).unwrap(); + + if let Ok(Some(record)) = rows.next() { + return Ok(false) + } + + Ok(true) + }) + .await + else { + return Err(Error::internal_with_message( + "Failed to execute query 'get_event_by_id'", + )); + }; + + query_result } } @@ -1196,7 +1272,7 @@ impl NostrDatabase for NostrSqlite { coordinate: &Coordinate, timestamp: Timestamp, ) -> Result { - todo!() + self.has_coordinate_been_deleted(coordinate, timestamp).await } /// Set [`EventId`] as seen by relay @@ -1245,7 +1321,9 @@ impl NostrDatabase for NostrSqlite { &self, filter: Filter, ) -> Result, Self::Err> { - todo!() + Err(Error::internal_with_message( + "negentropy is not currently supported", + )) } /// Wipe all data @@ -1577,4 +1655,45 @@ mod tests { assert_eq!(result.len(), 1) } + + #[tokio::test] + async fn save_event_with_a_tag() { + let config = Arc::new(ServiceConfig::new()); + let db = NostrSqlite::new(config).await; + + let keys = nostr::Keys::generate(); + let d_tag = nostr::Tag::Identifier("test".to_string()); + let coordinate_event = + nostr::EventBuilder::new(nostr::Kind::TextNote, "hello", vec![d_tag.clone()]) + .to_event(&keys) + .unwrap(); + db.save_event(&coordinate_event).await.unwrap(); + + let coordinate = nostr::nips::nip01::Coordinate::new( + nostr::Kind::CategorizedBookmarkList, + coordinate_event.pubkey, + ) + .identifier("test"); + + let a_tag = nostr::Tag::A { + kind: nostr::Kind::CategorizedBookmarkList, + public_key: keys.public_key(), + identifier: "test".to_string(), + relay_url: None, + }; + let event = + nostr::EventBuilder::new(nostr::Kind::CategorizedBookmarkList, "", vec![d_tag, a_tag]) + .to_event(&keys) + .unwrap(); + + let result = db.save_event(&event).await.unwrap(); + + let ts = nostr::Timestamp::now(); + let res = db + .has_coordinate_been_deleted(&coordinate, ts) + .await + .unwrap(); + + dbg!(res); + } }