tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 14550d1aaaf0e24c7ae49ec8711d80cf07b08beb
parent 837df617cb50696a759e6320495e91f616f10a2d
Author: triesap <tyson@radroots.org>
Date:   Fri,  5 Jun 2026 23:10:44 -0700

store-surreal: query listing projections

Diffstat:
Mcrates/tangle_store_surreal/src/lib.rs | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 174 insertions(+), 3 deletions(-)

diff --git a/crates/tangle_store_surreal/src/lib.rs b/crates/tangle_store_surreal/src/lib.rs @@ -695,6 +695,58 @@ pub enum SearchDocumentOutcome { Indexed, } +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ListingProjectionQuery { + effective_status: Option<String>, + seller_pubkey: Option<String>, + unit: Option<String>, + currency_norm: Option<String>, + min_price_minor: Option<i64>, + max_price_minor: Option<i64>, + limit: Option<u64>, +} + +impl ListingProjectionQuery { + pub fn new() -> Self { + Self::default() + } + + pub fn with_effective_status(mut self, value: &str) -> Self { + self.effective_status = Some(value.to_owned()); + self + } + + pub fn with_seller_pubkey(mut self, value: &str) -> Self { + self.seller_pubkey = Some(value.to_owned()); + self + } + + pub fn with_unit(mut self, value: &str) -> Self { + self.unit = Some(value.to_owned()); + self + } + + pub fn with_currency_norm(mut self, value: &str) -> Self { + self.currency_norm = Some(value.to_owned()); + self + } + + pub fn with_min_price_minor(mut self, value: i64) -> Self { + self.min_price_minor = Some(value); + self + } + + pub fn with_max_price_minor(mut self, value: i64) -> Self { + self.max_price_minor = Some(value); + self + } + + pub fn with_limit(mut self, value: u64) -> Self { + self.limit = Some(value); + self + } +} + #[derive(Clone)] pub struct SurrealStore { db: Surreal<Db>, @@ -1450,6 +1502,65 @@ UPSERT type::record('listing_current', $listing_key) CONTENT { response.take(0).map_err(SurrealStoreError::from) } + pub async fn query_current_listings( + &self, + query: &ListingProjectionQuery, + ) -> Result<Vec<serde_json::Value>, SurrealStoreError> { + let mut statement = + "SELECT * FROM listing_current WHERE hidden = false AND deleted = false".to_owned(); + if query.effective_status.is_some() { + statement.push_str(" AND effective_status = $effective_status"); + } + if query.seller_pubkey.is_some() { + statement.push_str(" AND seller_pubkey = $seller_pubkey"); + } + if query.unit.is_some() { + statement.push_str(" AND unit = $unit"); + } + if query.currency_norm.is_some() { + statement.push_str(" AND currency_norm = $currency_norm"); + } + if query.min_price_minor.is_some() { + statement.push_str(" AND price_minor >= $min_price_minor"); + } + if query.max_price_minor.is_some() { + statement.push_str(" AND price_minor <= $max_price_minor"); + } + statement.push_str(" ORDER BY updated_at DESC, event_id ASC"); + if query.limit.is_some() { + statement.push_str(" LIMIT $limit"); + } + statement.push(';'); + let mut surreal_query = self.db.query(statement); + if let Some(value) = &query.effective_status { + surreal_query = surreal_query.bind(("effective_status", value.as_str())); + } + if let Some(value) = &query.seller_pubkey { + surreal_query = surreal_query.bind(("seller_pubkey", value.as_str())); + } + if let Some(value) = &query.unit { + surreal_query = surreal_query.bind(("unit", value.as_str())); + } + if let Some(value) = &query.currency_norm { + surreal_query = surreal_query.bind(("currency_norm", value.as_str())); + } + if let Some(value) = query.min_price_minor { + surreal_query = surreal_query.bind(("min_price_minor", value)); + } + if let Some(value) = query.max_price_minor { + surreal_query = surreal_query.bind(("max_price_minor", value)); + } + if let Some(value) = query.limit { + surreal_query = surreal_query.bind(("limit", value)); + } + let mut response = surreal_query + .await + .map_err(SurrealStoreError::from)? + .check() + .map_err(SurrealStoreError::from)?; + response.take(0).map_err(SurrealStoreError::from) + } + pub async fn project_listing_helpers( &self, event: &Event, @@ -2207,9 +2318,10 @@ impl From<surrealdb::Error> for SurrealStoreError { mod tests { use super::{ CurrentEventOutcome, DeletionMarkerOutcome, ListingCurrentOutcome, ListingHelperOutcome, - ListingRevisionOutcome, MigrationApplyOutcome, SearchDocumentOutcome, SurrealConfigError, - SurrealConnectionConfig, SurrealConnectionMode, SurrealMigration, SurrealMigrationError, - SurrealMigrationPlan, SurrealStore, base_migration_plan, migration_tracking_schema, + ListingProjectionQuery, ListingRevisionOutcome, MigrationApplyOutcome, + SearchDocumentOutcome, SurrealConfigError, SurrealConnectionConfig, SurrealConnectionMode, + SurrealMigration, SurrealMigrationError, SurrealMigrationPlan, SurrealStore, + base_migration_plan, migration_tracking_schema, }; use tangle_protocol::{ Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, @@ -3695,6 +3807,65 @@ mod tests { } #[tokio::test] + async fn query_current_listings_applies_projection_filters() { + let store = memory_store().await; + store + .apply_plan(&base_migration_plan()) + .await + .expect("apply plan"); + let listing = build_fixture_event(&valid_public_listing_spec()).expect("listing"); + let listing_key = format!("30402:{}:listing-a", listing.unsigned().pubkey().as_str()); + store + .project_current_listing(&listing, UnixTimestamp::new(1_714_125_400)) + .await + .expect("project listing"); + + let query = ListingProjectionQuery::new() + .with_effective_status("active") + .with_seller_pubkey(listing.unsigned().pubkey().as_str()) + .with_unit("lb") + .with_currency_norm("USD") + .with_min_price_minor(1_000) + .with_max_price_minor(2_000) + .with_limit(5); + let rows = store + .query_current_listings(&query) + .await + .expect("listing query"); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0]["listing_key"], listing_key); + + let no_match = ListingProjectionQuery::new() + .with_effective_status("active") + .with_min_price_minor(2_000); + assert!( + store + .query_current_listings(&no_match) + .await + .expect("no match") + .is_empty() + ); + + store + .database() + .query("UPDATE listing_current SET hidden = true WHERE listing_key = $listing_key;") + .bind(("listing_key", listing_key.as_str())) + .await + .expect("hide listing") + .check() + .expect("hide check"); + assert!( + store + .query_current_listings( + &ListingProjectionQuery::new().with_effective_status("active") + ) + .await + .expect("hidden query") + .is_empty() + ); + } + + #[tokio::test] async fn project_listing_helpers_persists_discovery_tables() { let store = memory_store().await; store