lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 041f741267f91659af9715e0e45263fbe1351dca
parent b09fc74b5174ada6d41c3fede033e2f0655a8153
Author: triesap <tyson@radroots.org>
Date:   Sat, 25 Apr 2026 04:37:47 +0000

replica: add trade product listing address

- add nullable listing_addr storage for trade products
- expose listing_addr on product schema and summary rows
- cover present and addressless market query rows
- preserve legacy migration repair behavior for existing databases

Diffstat:
Acrates/replica_db/migrations/0020_trade_product_listing_addr.down.sql | 1+
Acrates/replica_db/migrations/0020_trade_product_listing_addr.up.sql | 1+
Mcrates/replica_db/src/migrations.rs | 5+++++
Mcrates/replica_db/src/query.rs | 5+++--
Mcrates/replica_db/tests/full_mode.rs | 20+++++++++++++-------
Mcrates/replica_db/tests/migration_repairs.rs | 8++++++++
Mcrates/replica_db_schema/src/models/trade_product.rs | 7+++++++
7 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/crates/replica_db/migrations/0020_trade_product_listing_addr.down.sql b/crates/replica_db/migrations/0020_trade_product_listing_addr.down.sql @@ -0,0 +1 @@ +ALTER TABLE trade_product DROP COLUMN listing_addr; diff --git a/crates/replica_db/migrations/0020_trade_product_listing_addr.up.sql b/crates/replica_db/migrations/0020_trade_product_listing_addr.up.sql @@ -0,0 +1 @@ +ALTER TABLE trade_product ADD COLUMN listing_addr TEXT; diff --git a/crates/replica_db/src/migrations.rs b/crates/replica_db/src/migrations.rs @@ -103,6 +103,11 @@ pub static MIGRATIONS: &[Migration] = &[ up_sql: include_str!("../migrations/0019_repair_missing_indexes.up.sql"), down_sql: include_str!("../migrations/0019_repair_missing_indexes.down.sql"), }, + Migration { + name: "0020_trade_product_listing_addr", + up_sql: include_str!("../migrations/0020_trade_product_listing_addr.up.sql"), + down_sql: include_str!("../migrations/0020_trade_product_listing_addr.down.sql"), + }, ]; pub fn run_all_up<E>(executor: &E) -> Result<(), SqlError> diff --git a/crates/replica_db/src/query.rs b/crates/replica_db/src/query.rs @@ -19,6 +19,7 @@ pub struct ReplicaTradeProductSummaryRow { pub price_currency: String, pub price_qty_amt: u32, pub price_qty_unit: String, + pub listing_addr: Option<String>, pub location_primary: Option<String>, } @@ -37,7 +38,7 @@ impl<E: SqlExecutor> ReplicaSql<E> { &self, lookup: &str, ) -> Result<Vec<ReplicaTradeProductSummaryRow>, SqlError> { - let sql = "SELECT tp.id, tp.key, tp.category, tp.title, tp.summary, tp.qty_amt, tp.qty_unit, tp.qty_label, tp.qty_avail, tp.price_amt, tp.price_currency, tp.price_qty_amt, tp.price_qty_unit, loc.location_primary \ + let sql = "SELECT tp.id, tp.key, tp.category, tp.title, tp.summary, tp.qty_amt, tp.qty_unit, tp.qty_label, tp.qty_avail, tp.price_amt, tp.price_currency, tp.price_qty_amt, tp.price_qty_unit, tp.listing_addr, loc.location_primary \ FROM trade_product tp \ LEFT JOIN (\ SELECT tpl.tb_tp AS trade_product_id, MIN(COALESCE(gl.label, gl.gc_name, gl.gc_admin1_name, gl.gc_country_name, gl.d_tag)) AS location_primary \ @@ -77,7 +78,7 @@ impl<E: SqlExecutor> ReplicaSql<E> { } let sql = format!( - "SELECT tp.id, tp.key, tp.category, tp.title, tp.summary, tp.qty_amt, tp.qty_unit, tp.qty_label, tp.qty_avail, tp.price_amt, tp.price_currency, tp.price_qty_amt, tp.price_qty_unit, loc.location_primary \ + "SELECT tp.id, tp.key, tp.category, tp.title, tp.summary, tp.qty_amt, tp.qty_unit, tp.qty_label, tp.qty_avail, tp.price_amt, tp.price_currency, tp.price_qty_amt, tp.price_qty_unit, tp.listing_addr, loc.location_primary \ FROM trade_product tp \ LEFT JOIN (\ SELECT tpl.tb_tp AS trade_product_id, MIN(COALESCE(gl.label, gl.gc_name, gl.gc_admin1_name, gl.gc_country_name, gl.d_tag)) AS location_primary \ diff --git a/crates/replica_db/tests/full_mode.rs b/crates/replica_db/tests/full_mode.rs @@ -118,6 +118,7 @@ fn full_mode_shaped_query_helpers_cover_cli_reads() { .gcs_location_create(&gcs_location) .expect("gcs create") .result; + let listing_addr = format!("30402:{}:listing-a", hex64('d')); let trade_product: ITradeProductCreate = parse_json(json!({ "key": "product-a", @@ -136,6 +137,7 @@ fn full_mode_shaped_query_helpers_cover_cli_reads() { "price_currency": "USD", "price_qty_amt": 1, "price_qty_unit": "kg", + "listing_addr": listing_addr.clone(), "notes": "fresh coffee" })); let trade_product_created = db @@ -167,6 +169,7 @@ fn full_mode_shaped_query_helpers_cover_cli_reads() { .expect("trade product search"); assert_eq!(rows.len(), 1); assert_eq!(rows[0].key, "product-a"); + assert_eq!(rows[0].listing_addr.as_deref(), Some(listing_addr.as_str())); assert_eq!(rows[0].location_primary.as_deref(), Some("stockholm")); let lookup_rows = db @@ -174,6 +177,10 @@ fn full_mode_shaped_query_helpers_cover_cli_reads() { .expect("trade product lookup"); assert_eq!(lookup_rows.len(), 1); assert_eq!(lookup_rows[0].id, trade_product_created.id); + assert_eq!( + lookup_rows[0].listing_addr.as_deref(), + Some(listing_addr.as_str()) + ); assert_eq!( db.trade_product_search(&[]).expect("empty search"), @@ -1120,13 +1127,12 @@ fn full_mode_crud_and_relation_paths() { let trade_product_find_many: ITradeProductFindMany = parse_json(json!({ "filter": { "id": trade_product_created.id } })); - assert_eq!( - db.trade_product_find_many(&trade_product_find_many) - .expect("trade product find many") - .results - .len(), - 1 - ); + let trade_product_results = db + .trade_product_find_many(&trade_product_find_many) + .expect("trade product find many") + .results; + assert_eq!(trade_product_results.len(), 1); + assert_eq!(trade_product_results[0].listing_addr, None); let trade_product_find_one: ITradeProductFindOne = parse_json(json!({ "on": { "id": trade_product_created.id } })); assert!( diff --git a/crates/replica_db/tests/migration_repairs.rs b/crates/replica_db/tests/migration_repairs.rs @@ -12,6 +12,7 @@ fn create_legacy_schema_without_secondary_indexes(exec: &SqliteExecutor) { "CREATE TABLE __migrations (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, applied_at TEXT NOT NULL DEFAULT (datetime('now')))", "CREATE TABLE farm (id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), d_tag TEXT NOT NULL, pubkey TEXT NOT NULL, name TEXT NOT NULL, about TEXT, website TEXT, picture TEXT, banner TEXT, location_primary TEXT, location_city TEXT, location_region TEXT, location_country TEXT)", "CREATE TABLE gcs_location (id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), d_tag TEXT NOT NULL, lat REAL NOT NULL, lng REAL NOT NULL, geohash TEXT NOT NULL, point TEXT NOT NULL, polygon TEXT NOT NULL, accuracy REAL, altitude REAL, tag_0 TEXT, label TEXT, area REAL, elevation INTEGER, soil TEXT, climate TEXT, gc_id TEXT, gc_name TEXT, gc_admin1_id TEXT, gc_admin1_name TEXT, gc_country_id TEXT, gc_country_name TEXT)", + "CREATE TABLE trade_product (id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), key TEXT NOT NULL, category TEXT NOT NULL, title TEXT NOT NULL, summary TEXT NOT NULL, process TEXT NOT NULL, lot TEXT NOT NULL, profile TEXT NOT NULL, year INTEGER NOT NULL, qty_amt INTEGER NOT NULL, qty_unit CHAR(4) NOT NULL, qty_label TEXT, qty_avail INTEGER, price_amt REAL NOT NULL, price_currency CHAR(3) NOT NULL, price_qty_amt INTEGER NOT NULL, price_qty_unit CHAR(4) NOT NULL, notes TEXT)", "CREATE TABLE plot (id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), d_tag TEXT NOT NULL, farm_id CHAR(36) NOT NULL, name TEXT NOT NULL, about TEXT, location_primary TEXT, location_city TEXT, location_region TEXT, location_country TEXT, FOREIGN KEY (farm_id) REFERENCES farm(id) ON DELETE CASCADE)", "CREATE TABLE nostr_event_state (id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), key TEXT NOT NULL UNIQUE, kind INTEGER NOT NULL, pubkey CHAR(64) NOT NULL CHECK(length(pubkey) = 64), d_tag TEXT NOT NULL, last_event_id CHAR(64) NOT NULL CHECK(length(last_event_id) = 64), last_created_at INTEGER NOT NULL, content_hash TEXT NOT NULL)", ]; @@ -83,4 +84,11 @@ fn run_all_up_repairs_missing_indexes_in_legacy_sqlite_dbs() { "plot_farm_d_tag_idx".to_string(), ] ); + + let trade_product_columns = query_rows(&exec, "PRAGMA table_info(trade_product)"); + assert!(trade_product_columns.iter().any(|row| { + row.get("name") + .and_then(Value::as_str) + .is_some_and(|name| name == "listing_addr") + })); } diff --git a/crates/replica_db_schema/src/models/trade_product.rs b/crates/replica_db_schema/src/models/trade_product.rs @@ -26,6 +26,7 @@ pub struct TradeProduct { pub price_currency: String, pub price_qty_amt: u32, pub price_qty_unit: String, + pub listing_addr: Option<String>, pub notes: Option<String>, } #[cfg_attr(feature = "ts-rs", derive(TS))] @@ -51,6 +52,8 @@ pub struct ITradeProductFields { pub price_qty_amt: u32, pub price_qty_unit: String, #[cfg_attr(feature = "ts-rs", ts(optional, type = "string | null"))] + pub listing_addr: Option<String>, + #[cfg_attr(feature = "ts-rs", ts(optional, type = "string | null"))] pub notes: Option<String>, } #[cfg_attr(feature = "ts-rs", derive(TS))] @@ -90,6 +93,8 @@ pub struct ITradeProductFieldsPartial { #[cfg_attr(feature = "ts-rs", ts(optional, type = "string | null"))] pub price_qty_unit: Option<serde_json::Value>, #[cfg_attr(feature = "ts-rs", ts(optional, type = "string | null"))] + pub listing_addr: Option<serde_json::Value>, + #[cfg_attr(feature = "ts-rs", ts(optional, type = "string | null"))] pub notes: Option<serde_json::Value>, } #[cfg_attr(feature = "ts-rs", derive(TS))] @@ -135,6 +140,8 @@ pub struct ITradeProductFieldsFilter { #[cfg_attr(feature = "ts-rs", ts(optional))] pub price_qty_unit: Option<String>, #[cfg_attr(feature = "ts-rs", ts(optional))] + pub listing_addr: Option<String>, + #[cfg_attr(feature = "ts-rs", ts(optional))] pub notes: Option<String>, } #[cfg_attr(feature = "ts-rs", derive(TS))]